MenuItems Checked State

Topics: CAB & Smart Client Software Factory
Dec 12, 2006 at 4:01 AM
originally posted by: Dec2000

Hi all,

I have the following code that creates a menu item, adds it to the Reports menu and adds an invoker to handle the click event. The click event also fires. However I want to also set the menuItem's checked state to True, to show that it has been clicked. Howver I cannot get a reference to the MenuItem to set the checked state, is it possible to do this? I'm sure it is but have no idea where to begin.

Dim item As ToolStripMenuItem = New ToolStripMenuItem("Run Report")
RootWorkItem.UIExtensionSites("Reports").Add(item)
Commands("RunReport").AddInvoker(item, "Click")

<CommandHandler("RunReport")> _
Public Sub ShowCustomerHandler(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Running Report")
End Sub
Dec 12, 2006 at 4:25 AM
originally posted by: PJackson

Our solution to this was to create a custom Command and a WorkItemExtension to synchronize the checked status of all invokers (for instance a menu item and toolbar button both invoking the same command, we would want both to check/uncheck together). We used an attribute to identify a command class should keep checks synchronized, since we didn't want to require a particular inheritance structure for the functionality:

AttributeUsage(AttributeTargets.Interface
public sealed class CheckSynchronizeAttribute : System.Attribute
{
}

CheckSynchronizeAttribute
public sealed class CheckSynchronizedCommand : Command
{
}

These two classes are empty, since they're only used to identify a command that should do this, the work occurs in a WorkItemExtension:

*** code to follow ***

Usage is to add a Command to the WorkItem collection of this type:

WorkItem.Commands.AddNew<CheckSynchronizedCommand>("MyCommand")

A limitation of this method is that the Command must be added to the WorkItem PRIOR to any CommandHandlers being identified at startup. When a CommandHandler attribute is processed, it adds a base Command to the collection with the name and it can't then be replaced with the synchronized Command.
Dec 12, 2006 at 4:26 AM
originally posted by: PJackson

/// <summary>
/// Extends all WorkItems to check their command collection for commands that should have checked property synchronized
/// </summary>
WorkItemExtension(typeof(WorkItem))
public sealed class CheckSynchronizerWorkItemExtension : WorkItemExtension
{


protected override void OnInitialized()
{
base.OnInitialized();
WorkItem.Commands.Added += new EventHandler<Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command>>(onCommandAdded);
WorkItem.Commands.Removed += new EventHandler<Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command>>(onCommandRemoved);
foreach (KeyValuePair<string,Command> item in WorkItem.Commands)
{
Command cmd = item.Value;
if ((WorkItem.Parent == null) || !(WorkItem.Parent.Items.ContainsObject(cmd)))
{
if (cmd.GetType().GetCustomAttributes(typeof(CheckSynchronizeAttribute), true).Length > 0)
cmd.ExecuteAction += onCommandExecuted;
}
}

}

protected override void OnTerminated()
{
base.OnTerminated();
WorkItem.Commands.Added -= new EventHandler<Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command>>(onCommandAdded);
//WorkItem.Commands.Removed -= new EventHandler<Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command>>(onCommandRemoved);
}

/// <summary>
/// Unlinks the command's execute to onCommandExecuted
/// NOTE: This code should never execute, as it is forbidden to remove a command from a workitem
/// </summary>
/// <param name="sender"></param>
/// <param name="e">Command</param>
private void onCommandRemoved(object sender, Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command> e)
{
//if (e == null || e.Data == null) return;

//if (e.Data.GetType().GetCustomAttributes(typeof(CheckSynchronizeAttribute), true).Length < 1) return;

//e.Data.ExecuteAction -= new EventHandler(onCommandExecuted);
}
Dec 12, 2006 at 4:26 AM
originally posted by: PJackson

/// <summary>
/// Links the command's execute to onCommandExecuted
/// </summary>
/// <param name="sender"></param>
/// <param name="e">Command</param>
private void onCommandAdded(object sender, Microsoft.Practices.CompositeUI.Utility.DataEventArgs<Microsoft.Practices.CompositeUI.Commands.Command> e)
{
if (e == null || e.Data == null) return;

if (e.Data.GetType().GetCustomAttributes(typeof(CheckSynchronizeAttribute), true).Length < 1) return;

e.Data.ExecuteAction += new EventHandler(onCommandExecuted);
}

/// <summary>
/// When the command executes, its adapters will be checked/unchecked as appropriate
/// </summary>
/// <param name="sender">Executing command</param>
/// <param name="e"></param>
private void onCommandExecuted(object sender, EventArgs e)
{
Command command = sender as Command;
if (command == null) return;

foreach (ToolStripItemCommandAdapter adapter in command.FindAdapters<ToolStripItemCommandAdapter>())
{
foreach (KeyValuePair<ToolStripItem, List<string>> invokerItem in adapter.Invokers)
{
ToolStripMenuItem menu = invokerItem.Key as ToolStripMenuItem;
if (menu != null)
{
menu.Checked = !(menu.Checked);
}
else
{
ToolStripButton button = invokerItem.Key as ToolStripButton;
if (button != null)
button.Checked = !(button.Checked);
}
}
}
}

}
Dec 12, 2006 at 6:20 AM
originally posted by: hmoeller

Isn't this loading order requirement a pretty serious restriction? I wouldn't recommend to set up such constraints since you cannot always be sure that this isn't broken by some new module which is later added to the profile catalog.

I've done this by examining the command and its' invokers directly:

private ToolStripMenuItem[] GetInvokingMenuItems(Command aCommand)
{
List<ToolStripMenuItem> miList = new List<ToolStripMenuItem>();

ReadOnlyCollection<ToolStripItemCommandAdapter> coll = aCommand.FindAdapters<ToolStripItemCommandAdapter>();
foreach (ToolStripItemCommandAdapter commandAdapter in coll)
{
foreach (object obj in commandAdapter.Invokers.Keys)
{
if (obj is ToolStripMenuItem)
{
miList.Add(obj as ToolStripMenuItem);
}
}
}

return miList.ToArray();
}
Dec 12, 2006 at 6:28 AM
originally posted by: Dec2000

Thanks guys for the help, used a way similar to hmoeller but from the previous post.
Dec 12, 2006 at 7:06 AM
originally posted by: JKraft4PIT

I personally being a newer programmer (only a year of real world experirence) would go this route.

I would create the menu Item and keep a reference of it in your workitem.
//Sorry I write code in C#
private ToolStripMenuItem RunReportMenuItem = null;
//Then when you create it to add it to the Commands collection. maybe in the initialized or OnRunStarted.
{
RunReportMenuItem = New ToolStripMenuItem"Run Report";
RootWorkItem.UIExtensionSites"Reports".Add(item);
Commands"RunReport".AddInvoker(item, "Click");
}


CommandHandler("RunReport")
Public void ShowCustomerHandler(Object sender , EventArgs e)
{
MessageBox.Show("Running Report");

//Here you can flip the checked property of the menu Item
RunReportMenuItem.Checked = !RunReportMenuItem.Checked;
}

**//Edit It does actually work I was using a method to wire it up and it created a new reference, silly me.//**

//Well I just tested this and it doesn't work. I also tried doing this:

//RunReportMenuItem.CheckonClick = True;

//That line is supose to make it automatically check/unchekc the menu Item. But doesn't seem to work.

//Does CAB interfere with this functionality?

Jordon
Dec 13, 2006 at 10:29 AM
originally posted by: ChrisHolmes

Here's how we've solved the MenuItem/ToolStripItem issue.

We created a Service called IUIElementExtensionService. It's job is to hold references to menuitems and toolstripitems. So when WorkItems create new menuitems or toolstripitems to display, they must use this service.

Then we use this service to unload/reload the toolstrip and menustrip when a certain WorkItem gets focus from the user. This has also solved the "how do I notify the correct Workitem when a "Save" button is clicked" problem, because we dont' use global toolstrip or menustrip items. Each WorkItem is responsible for creating its own "Save" button - if it needs it. Our Modules and Workitems are completely unaware of anything global or provided by the shell. They just know about the IUIElementExtensionService. To them, the MenuStrip and ToolStrip are virgin territory. They don't know about other modules and workitems and how they use it.