I am almost certainly missing something completely obvious. I have a singleton main form with various menu items with shortcuts. I have child windows which can be embedded on the main form or floating on their own. The main form class has a static member that points to the one extant main form, so the child windows can access its public functions. I want the hotkeys normally linked to the toolbar entries to work from the child windows, and I'd prefer not to duplicate the code. I know I have to invoke a keypress event on the main form, but I am running a complete blank today.
To give a simple example, there are menu items to save the current file and to center the window on the point the mouse is at, triggered by Ctrl+S and Ctrl+E respectively. They're set up as keyboard shortcuts in my Main Form, but the actual heavy lifting is done by my child window for the latter command. I have it temporarily fixed by catching a KeyDown event in my child window, but that means two different places that the same shortcut shows up.
There seem to be few solutions to handle key shortcuts.
One of them could be installing keyboard hooks as suggested here.
You can also try to handle this by adding custom message filter, as suggested here, however, I haven't verified the code posted there.
The solution with hooks seems to be a little tricky, so You may want to try custom message filter first.
As per my comments to Lukasz M above, I needed to maintain the current menu structure for legacy reasons, namely that all of the engineers are accustomed to changing the Menu, and they want the menu shortcuts to automatically work. I could probably modify the central hotkey location using that custom message filter to generate Shortcut text for the Menu items, but that would add additional complexity that's likely to be undone the next time someone steps in to add a quick menu item. Thus, I went with the solution I alluded to in the comments with an invisible menu for the child windows.
Much to my surprise, if a menu has its Visible property set to false, the hotkeys work just fine. And the events associated with menu items in the main form work perfectly fine if invoked from a child form, as they're executed relative to the window where they are defined rather than by the window where they're invoked. Thus, my first solution was to fetch the MenuStrip from the main form and add it to the child form. That didn't work, as turning it invisible in the child form made it invisible in the main form as well. My next attempt involved creating a new hidden MenuStrip and adding the ToolStripMenuItem items from the main form's MenuStrip onto it. That broke the hotkey capability, possibly since the menu items now existed in more than one place. Finally, I created shallow copies of the menu items that contained only the shortcut key, the Tag property (necessary for a few menu items that made use of it), and the Event Handler (the last being done through the method described in How to clone Control event handlers at run time?). After a bit of fiddling, I came to realize that I didn't need to maintain the structure of the menus and I only needed the items with shortcuts. This is what I wound up with:
Main Form:
/// <summary>
/// Returns copies of all menu shortcut items in the main form.
/// </summary>
/// <returns>A list containing copies of all of the menu items with a keyboard shortcut.</returns>
public static List<ToolStripMenuItem> GetMenuShortcutClones()
{
List<ToolStripMenuItem> shortcutItems = new List<ToolStripMenuItem>();
Stack<ToolStripMenuItem> itemsToBeParsed = new Stack<ToolStripMenuItem>();
foreach (ToolStripItem menuItem in mainForm.menuStrip.Items)
{
if (menuItem is ToolStripMenuItem)
{
itemsToBeParsed.Push((ToolStripMenuItem)menuItem);
}
}
while (itemsToBeParsed.Count > 0)
{
ToolStripMenuItem menuItem = itemsToBeParsed.Pop();
foreach (ToolStripItem childItem in menuItem.DropDownItems)
{
if (childItem is ToolStripMenuItem)
{
itemsToBeParsed.Push((ToolStripMenuItem)childItem);
}
}
if (menuItem.ShortcutKeys != Keys.None)
{
shortcutItems.Add(CloneMenuItem(menuItem));
}
}
return shortcutItems;
}
/// <summary>
/// Returns an effective shortcut clone of a ToolStripMenuItem. It does not copy the name
/// or text, but it does copy the shortcut and the events associated with the menu item.
/// </summary>
/// <param name="menuItem">The MenuItem to be cloned</param>
/// <returns>The newly generated clone.</returns>
private static ToolStripMenuItem CloneMenuItem(ToolStripMenuItem menuItem)
{
ToolStripMenuItem copy = new ToolStripMenuItem();
copy.ShortcutKeys = menuItem.ShortcutKeys;
copy.Tag = menuItem.Tag;
var eventsField = typeof(Component).GetField("events", BindingFlags.NonPublic | BindingFlags.Instance);
var eventHandlerList = eventsField.GetValue(menuItem);
eventsField.SetValue(copy, eventHandlerList);
return copy;
}
Child Form:
private void OnRefresh(object sender, EventArgs e)
{
// Refresh the hiddenShortcutMenu.
List<ToolStripMenuItem> shortcutList = MainForm.GetMenuShortcutClones();
hiddenShortcutMenu.Items.Clear();
hiddenShortcutMenu.Items.AddRange(shortcutList.ToArray());
}
Inside the child form's constructor, I instantiated the hiddenShortcutMenu, set Visible to false, assigned it to my child form's control, and set the event. The last was a bit fiddly in that I had to have it periodically refresh since the menus sometimes changed according to context. Currently, I have it set to the Paint event for maximum paranoia, but I think I'm going to try to find a way for the main form to signal that it's changed the menu structure.
Related
It happens in all my WPF applications and Visual Studio 2019 (which is definitely WPF based) also exhibits this strange behavior:
Simply right click an item in the solution explorer while holding, say, the Control key and you should notice that the items highlighting will intermittently work if you keep holding the Control modifier.
At first I presumed that the grids and lists controls were still catching the modifier keys for the items selection but this issue also occurs with a context menu on a simple control like a standard button.
Is there a way to fix this glitch?
Here is a gif with wpf application context menu in action. First I move mouse normally, then with Ctrl hold down:
As you can see it glitch (is not highlighting menu items).
MenuItem sets an internal flag when a key is pressed that stops mouse events from being registered temporarily, regardless of if it is actually a key that performs navigation. This reflects the behavior that I see, which is that the selection stutters when I hold down any key, not only modifiers. https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/windows/Controls/MenuItem.cs,2049
Since it's private there is no proper way to fix this. However if it is critical you can add a KeyDown handler for all your MenuItems and then change it with reflection
var menu = sender as MenuItem;
if (menu != null)
{
var parent = ItemsControl.ItemsControlFromItemContainer(menu);
MethodInfo setBoolField = menu.GetType().GetMethod("SetBoolField",
BindingFlags.NonPublic | BindingFlags.Static);
setBoolField.Invoke(this, new object[] { parent, 0x04, false });
}
If you want you can first check if the key was a navigation key to retain the desired behavior.
I would personally consider this a bug in WPF.
In my application I have a Form that for one of the top level menu items the sub menu items are dynamically created. So the goal is I have always have a top level menu item called "Actions" but the sub items are dynamically generated. The problem I'm having is that for every sub item I add the entire MainMenu repaints and flickers. The problem is more noticeable the more sub menu items that are added due to more paint calls I'm guessing, for instance if I'm only adding one sub menu item it doesn't flicker often but if I'm adding 15 (which is typical for my application) it's noticeable pretty much everytime. Note that this is using MenuItems rather than MenuStrip, we have custom controls developed to support MenuItems and haven't yet transitioned to use MenuStrip.
I created a simple example to demonstrate this where I have one button click that adds 15 sub items to each top level menu item (I have two in my demo "File" and "Edit") and you'll see it calls "DrawMenuBar" 30 times. Here's what the code is doing on a button click:
private void CreateSubMenus()
{
foreach (MenuItem menu in this.Menu.MenuItems)
{
for (int i = 1; i <= 15; i++)
{
MenuItem newSubMenu = new MenuItem();
newSubMenu.Text = "TestSub" + i;
menu.MenuItems.Add(newSubMenu);
}
}
}
Can't embed images as a new user but here's a link to what profiling shows Profiling Adding Sub Menus
So DrawMenuBar is called 30 times.
I've tried using something like:
this.Menu.MenuItems[0].AddRange(
but this ends up causing the same amount of draw calls. The best I could come up with is removing the top level menu item from the MainMenu then making the changes needed, then relinking it, but this also doesn't look that great because the menu item disappears than reappears. Ideally there would be no flickering or any change to the visibility of the top level menu bar as I'm not changing anything there but rather the sub menu items.
Basically, looking if there's an equivalent way to do a Suspend/Resume Layout but for the MainMenu object.
I see it. You'll need to reach for an ugly reflection hack to work around that. This worked, at least with your sample code:
var pi = typeof(MainMenu).GetField("form", BindingFlags.NonPublic | BindingFlags.Instance);
object form = pi.GetValue(this.Menu);
pi.SetValue(this.Menu, null);
// etc..
pi.SetValue(this.Menu, form);
Time to start supporting MenuStrip perhaps.
Try to use SuspendLayout() -> ResumeLayout() methods calls. In the middle of them write the your menu items adding code. By the way, follow the link to see concrete code example on MSDN.
I have a parent control (main form) and a child control (user control). The child control has some code, which determines what functions the application can perform (e.g. save files, write logs etc.). I need to show/hide, enable/disable main menu items of the main form according to the functionality. As I can't just write MainMenu.MenuItem1.Visible = false; (the main menu is not visible from the child control), I fire an event in the child control and handle this event on the main form. The problem is I need to pass what elements of the menu need to be shown/hidden. To do this I created an enum, showing what to do with the item
public enum ItemMode
{
TRUE, FALSE, NONE
}
Then I created my eventargs which have 6 parameters of type ItemMode (there are 6 menu items I need to manage). So any time I need to show the 1st item, hide the 2nd and do nothing with the rest I have to write something like this
e = new ItemModeEventArgs(ItemMode.TRUE, ItemMode.FALSE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE);
FireMyEvent(e);
This seems like too much code to me and what's more, what if I need to manage 10 items in future? Then I will have to rewrite all the constructors just to add 4 more NONEs.
I believe there's a better way of doing this, but I just can't figure out what it is.
you could create an EventArgs which takes an ItemMode[] or a List<ItemMode> or a Dictionary<string, ItemMode> for those items (instead of the current 6 arguments) - that way you don't need to change much when adding more items...
The chain child->parent can be reversed. In such scenario requests will be passed from the mainform to its child controls.
Controls participating in the command processing must implement a special interface:
interface ICommandHandler
{
bool CanInvoke(int commandId);
void InvokeCommand(int commandId);
bool UpdateCommand(int commandId, MenuItem item);
}
The advantage of this approach is that only active controls must be traversed, not all the children.
The weak point - UpdateCommand() method, which could be called from Application.Idle event or timer.
hope this helps
Well, I can't speak to a "best" way unless except in specific cases, since there are often several equally good ways. My first thought, though, would be to create a class that has a property which the parent assigns a reference of its MainMenu, and which has functions for enabling/disabling individual menus or items. In a very simple case, this could be as simple as passing a list of strings like "OptionsScreen=enabled" etc. and then inside the class manually handling those cases, to something more generic like passing strings such as "mnuToolsOptions=enabled" and then finding the menu item via the .Name property. So, on startup, create an instance of your menu handler class, then do something like MenuHandlerHelper.MenuToHandle = MainMenuStrip;.
On the child side, you could perhaps have your classes that update the MainMenu be derived UserObjects that derive from a common one you create that has a public MyMainMenuHandlerHelper MenuHandlerHelper property, and set that in your Parent form's constructor so the Child controls could call the menu updating function. Or, you could have an event that just passed back a List<string> containing all the rules, and fire that as you are doing now.
This is a very simple idea, and doesn't handle things like possible collisions, so you would probably either want to throw an exception (easiest). You might also want to have rule priorities (easy), or try to chain functionality (could be hard to determine orders and such).
I would be happy to implement some examples of my thinking if you can constrain the problem a little for me (desired collision handling, etc.) and I actually wanted to see what some basic code would look like and try perhaps to test a couple of ideas, so if those come to anything I will post the code here for those as well.
If you want to handle all changes from the user control: you could inherit your own user control class and add a reference to the form/collection of menu entries you want to be able to modify. You would pass this reference to its constructor and then you'll be able to easily modify the menu from inside your user control
If, on the other hand, you would like to manage this on an event basis in your form, you could implement your own EventArgs class, but I would do it like this:
class ItemModeEventArgs
{
MenuItemClass target;
EnumType change;
}
So basically for each menu item a separate event is risen. Every event args knows about what item menu is changing and how it is changing. Ofc, if you only have two states for the menu items, the 'change' field is kinda useless.
This way you don't have to hardcode functions with n parameters where n is the number of menu items.
There truly are many ways this could be done. The easiest way, although some will shout "bad practice", would be to just pass a pointer to the main menu when the control is created. Your control would have some code like this:
MenuStrip MainMenu;
internal void SetMainMenu(MenuStrip mainMenu)
{
MainMenu = mainMenu;
}
and when you create the control:
void CreateControl()
{
MyUserControlType MyControl = new MyUserControlType();
MyControl.SetMainMenu(mainMenuStrip); //or whatever you called your main menu
}
This will give your child form unlimited access to the mainform's menu (which is why it's technically a bad practice). From the child form you can access the submenus by name, eg:
if (MainMenu != null)
{
ToolStripMenuItem fileMenu =
(ToolStripMenuItem)MainMenu.Items["fileToolStripMenuItem"];
fileMenu.DropDownItems["exportFileToolStripItem"].Visible = false;
}
If you created the control in the designer, then you can add the SetMainMenu call into the .design file, or add it in the Form's load event.
My application has the following UI configuration:
The main form is an MDI container. Its child forms are attached to a TabStrip.
Each user has his set of child forms. Depending on the active user, only that user's child forms are displayed, together with tabs.
This is achieved by going through the main form's MdiChildren and setting their Visible property to false/true depending on the active user.
foreach (Form item in MdiChildren)
{
if (((OfficeFormEx)item).UserID == (int)e.NewTab.Tag)
{
item.Visible = true;
}
else
{
item.Visible = false;
}
}
This has two undesired effects. One is that every child form gets redrawn in succession, which is ugly and slow. The other is that for some reason the forms go from maximized to normal, effectively undocking them from the main form.
Is there any way to display just one of the child forms, such as the one the user was previously looking at, and get the others to stay in the background? The maximize/normal thing is not that big a deal because I can maximize them again manually.
Your question isn't very clear without a code snippet. You are however doing battle with the Windows MDI implementation. One thing it doesn't support is hiding a child window, it can only be minimized at best. Windows Forms implements the Visible property by destroying the Window handle, recreating it when the Visible property is set to True again. That new instance of the window won't be maximized.
It also doesn't support switching the focus to a child window when the current one is maximized. WF's workaround for that is to force the active child window back to the Normal state.
The MDI model is simply not very suitable for displaying child windows in the maximized state. To get a tabbed interface, use a TabControl and display UserControls on its tab pages.
At first sight, I would take a look at the Form.WindowsState property, if you haven't already. I doubt that if you happen to set this property to FormWindowState.Maximized on design, this would be changed when setting their Visible property true/false.
For the "[...] every child form gets redrawn in succession [...]"-thing, have you tried to use the SuspendLayout() method on the beginning of the active user's forms verification, and calling ResumeLayout() afterwards?
EDIT #1
I would advise you to load only the required Form for the current user.
Doing so will reduce the amount of memory used by your application, plus, it shall reduce considerably the number of forms contained within the MdiChildren collection property. Then, iterating through the collection, if still required, will be faster.
If this isn't an option for you, then perhaps using Linq might help:
var visibleForms = from f in MdiChildren
where (((OfficeFormEx)f).UserID == (int)e.NewTab.Tag)
select f;
var invisibleForms = from f in MdiChildren
where (((OfficeFormEx)f).UserID != (int)e.NewTab.Tag)
select f
visibleForms.ToList().ForEach(f => f.Visible = true);
invisibleForms.ToList().ForEach(f => f.Visible = false);
If you're using .NET 4.0, perhaps would this be a good candidate for PLINQ
Please provide feedback so that we can come to a solution. =)
I eventually solved this one so here's a belated write-up.
Will Marcouiller suggested SuspendLayout() and ResumeLayout(), which did not work. This led me to investigate what these two methods actually do and reach the conclusion that what I needed was a way to stop the main form from redrawing while operations on the MDI children were in progress.
This in turn resulted in the following two static utility methods which suspend redrawing for a given control. In my case, suspending redrawing of the main form resulted in a massive speed up.
/// <summary>
/// suspends drawing on a control and its children
/// </summary>
/// <param name="parent"></param>
public static void SuspendDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
}
/// <summary>
/// resumes drawing on a control and its children
/// </summary>
/// <param name="parent"></param>
public static void ResumeDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, true, 0);
control.Refresh();
}
I have a form. This form has a user control. This user control has a panel and a context menu. The context menu is not attached to the panel. There are other controls that are dynamically created and added to this panel. One of those controls is a button. When you click this button, I set the contextmenustrip property to my context menu.
My problem is that I need to read the items in that context menu prior to there being the opportunity to attach the context menu to the button.
Each time a form is loaded, I iterate though all the child controls of the form. If a control has children, I iterate through those, and so on... I can't seem to get at the context menu that is unassigned so to speak. It has not been attached to any control so it does not appear to be a child control of any controls on the form.
myConectMenu is never added to the user conrol like this.Controls.Add(myConectMenu). How can that context menu not be nested in the forms control collection? How can I get at that context menu?
Here is the designer code:
private System.Windows.Forms.ContextMenuStrip myContextMenu;
void InitializeComponent()
{
this.myContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
this.myContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.myToolStripMenuItem1,
this.myToolStripMenuItem2});
this.myContextMenu.Name = "myContextMenu";
this.myContextMenu.Size = new System.Drawing.Size(158, 92);
}
Update
The control iteration happens in a base class from which all forms in my application derive.
There is a private components object that the myContextMenu is added to. I imagine this is there so you can see the context menu in design view when it's not attached to a control. Perhaps I could leverage this?
private System.ComponentModel.IContainer components = null;
this.myContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
As you correctly observed, myContextMenu is not added to the Controls connection. Control has ContextMenuStrip property which you should check.
public void FindContextMenuStrip(Control input)
{
foreach(Control control in input.Controls)
{
if(control.ContextMenuStrip != null)
DoSomethingWithContextMenuStrip(control.ContextMenuStrip)
if(control.Controls.Count > 0)
FindContextMenuStrip(control);
}
}
Put relevant code in DoSomethingWithContextMenuStrip method.
EDIT:
I saw your comment where you specified what you wanted to do with ContextMenuStrip.
How about creating a method in Base class which takes user details and creates a context menu strip?
public ContextMenuStrip GetContextMenuStripForUser(User user)
{
//code to create context menu strip, with only those items enabled for which user has access.
}
In your final form, use this method to get ContextMenuStrip.
Create a custom contextmenu (SecureContextMenu in my case) that derives from contextmenu. Implement the open event and iterate through the items collection disabling the items that are not authorized.
Be sure to create a HasBeenOpened property and set it to true the first time the open event fires so that you don't have to keep checking the same controls every time the context menu is opened.
Use the SecureContextMenu everywhere you want context menu items checked against the list of authorized items.
It's a component and not a control attached to the form. Compare it to another form: you can manually .Show() a form from another form, but neither of them will show in in each other's .Control collection. Well, maybe that analogy wasn't the best... :s