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
Related
I am looking to have a user definable menu bar that contains a list of "favorite" actions that they can perform. I have a predefined menu of buttons which will perform different actions (open a new tab with content, update a record, etc.). I want the user to be able to select one of this controls, and choose to add it to their "favorite actions" which is a toolbar that will sit at the top of the page (this is currently done with a context menu). Ideally, the user will also be able to reorder this list of favorite actions.
Thus far, I've tried to use an ObservableCollection and List which is bound to a list view to tackle the first part of the problem. on the click method of the context menu, I have the following:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mnu = sender as MenuItem;
Button MyButton = null;
if (mnu != null)
{
ContextMenu MyContextMenu = (ContextMenu)mnu.Parent;
MyButton = MyContextMenu.PlacementTarget as Button;
}
dc.menuitems.Add(MyButton);
}
The list works when I add an object, however it did have issues firing INPC. When using an observable collection, I get the following error:
System.ArgumentException: 'Must disconnect specified child from current parent Visual before attaching to new parent Visual.'
I suspect this may be due to the fact that I'm somehow not creating a copy of the element, but rather reassigning it.
Is my approach the best approach? If so, how do I go about resolving the error that I see? What would be the best way to handle reordering of the items? I haven't been able to find much helpful information on creating such a control.
Once I've resolved this issue, I intend to use either a JSON or XML serializer to store this collection in the user settings to have their favorites stay persistent across application launches. Is this the best way to store this information?
I am implementing context-sensitive help for an existing WinForms app built in Visual Studio .NET. I have added a HelpProvider to the form and set the HelpNamespace property to a wonderful .chm that covers every control and menu item on the form. I have set the necessary HelpKeyword on all the controls that derive from Control and so far all is great: F1 works perfectly.
My problem is that I can't work out how to do it for menu items. These use the ToolStripMenuItem class, which does not derive from Control and so has no HelpKeyword property. How should I provide context-sensitive help for individual menu items? Mr. Google has not been very forthcoming.
Using F1 is not a common way of providing help for menu items. Menu items usually use ToolTip, or show some help text in StatusBar or usually their comprehensive helps comes with Help content of main page.
I prefer to use one of above mentioned solutions, but here for learning purpose, I'll show what you can do using HelpRequested event of the form.
To handle help for form and controls, you can rely on the HelpRequested event of the form and controls.
Here you can rely on Form event to solve the problem. Since you have a HelpProvider on form, you should know HelpProvider handles HelpRequested event of all controls internally and, for controls having ShowHelp set to true, it sets Handled to true and prevents bubbling the event up so you can not have your custom code for handling help event if ShowHelp is true. So you should set ShowHelp for controls to false and just use HelpProvider as a help key holder.
To solve the problem using the HelpRequested event of the form, you should follow these steps:
For ToolStripMenuItems, use the Tag property as the help key holder.
For other controls, if you use HelpProvider to assign HelpKey, don't forget to set ShowHelp to false.
Handle the HelpRequested event of the form.
In the body of event handler, check if there is an active menu item on your form, then use the Tag property of the active item to show help. If there is not any active menu, use the ActiveControl property of the form to show the help.
Example
Here is a step by step example of how you can show help for menu items using F1 key. To do so, follow these steps:
Create Form, Menu and Controls - Create a Form and put some controls and a MenuStrip having some menu and sub menus on the form.
Configuring HelpProvider - Put a HelpProvider control on form and for each control assign suitable key to HelpKeyword property of control. Also set ShowHelp for each control to false. We will handle help in code.
Configuring Help for Menu - For a ToolStripMenuItem use its Tag property to store the help keyword.
Creating a helper method to find descendants of the Menu - Add a class to your application having the following code. In the following code, I've introduced an extension method to get all sub ToolStripMenuItem of a MenuStrip:
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
public static class ToolStripMenuItemExtensions
{
public static List<ToolStripMenuItem> Descendants(this MenuStrip menu)
{
var items = menu.Items.OfType<ToolStripMenuItem>().ToList();
return items.SelectMany(x => Descendants(x)).Concat(items).ToList();
}
public static List<ToolStripMenuItem> Descendants(this ToolStripMenuItem item)
{
var items = item.DropDownItems.OfType<ToolStripMenuItem>().ToList();
return items.SelectMany(x => Descendants(x)).Concat(items).ToList();
}
}
Handling the Helprequested event to show help - Handle the HelpRequested event of the form and implement the algorithm which I described above using the following code:
private void Form1_HelpRequested(object sender, HelpEventArgs hlpevent)
{
string keyword = "";
var selectedMenuItem = this.menuStrip1.Descendants()
.Where(x => x.Selected).FirstOrDefault();
if (selectedMenuItem != null)
keyword = selectedMenuItem.Tag?.ToString();
else if (ActiveControl != null)
keyword = helpProvider1.GetHelpKeyword(ActiveControl);
if (!string.IsNullOrEmpty(keyword))
Help.ShowHelp(this, "Help.chm", HelpNavigator.Index, keyword);
}
Note
For testing the solution you don't need a chm file having index and so on. You can simply show the helpkeyword in Text property of form. It means the solution is working and after that you can create suitable chm file.
You can use one of the other overloads of ShowHelp method of Help class based on your requirement.
There are HelpKeyword and HelpString extended properties for controls, pay attention which one you are using and get the same one in the HelpRequested event.
Don't forget to set ShowHelp to false. If you forget this step, the event will be handled internally in Helpprovider.
Don't forget to assign a help keyword to Tag property of menu items. To make it more friendly for future, you can simply create an extender provider that adds a help keyword property to menu items.
In my form I have a TabControl container. Is there a way to place a button that looks like a tab itself on the right side of the already existing tabs? Furthermore the behaviour of the button has to be dynamic (move to the right as new tabs get added, move to the left as existing tabs are deleted).
Thanks in advance.
define class variable
TabPage addTab=new TabPage("+");
then (in the from constructor after initialization, or in form load event) add this tab page to you tabcontroll
tabControll.TabPages.Add(addTab);
then in SelectedIndexChanged event of your tabccontroll
if (tabControll.SelectedTab==addTab){
var index=tabControll.TabPages.Count-1;
var myNewTab=new TabPage("title");
//what ever you want to do with the tab
tabControll.TabPages.Insert(index,myNewTab);
tabControll.SelectedTab=myNewTab;
}
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.
I'm trying to come up with a solution to create dynamic context menus that can be generated at run time. I have implemented a IGuiCommand interface that implements something similar to the normal Command pattern.
interface IGuiCommand
{
Execute();
Undo();
bool CanUndo {get;set;}
Redo();
string CommandName {get;set;}
string CommandDescription {get;set;}
}
The idea is to allow the control that is Right Clicked to submit it's own list of commands to display in a given context menu.
While I could have each control build a context menu, I would prefer to use a single context menu and dynamicly generate the menu to allow easier management during run time. When a control state or application state changes, I would like the context menu to reflect the change. For example if I right click a checkbox, then the checkbox would submit to the context menu an Enable or Disable command to display depending on the checkbox's current Checked value.
I think I could easily implement this if I had some way to know which control was "Right Clicked" in order to bring up the context menu for that specific control.
It seems surprising the ContextMenu events doesn't supply an EventArg that indicates the control that was right clicked (or whatever command that would cause the context menu to Popup)
You just need to override the ContextMenuStrip_Opening event. The sender object is a ContextMenuStrip, which contains a SourceControl element. When you apply the proper cast, you will have access to everything you need.
private void contextMenuStrip1_Opening(object sender, System.ComponentModel.CancelEventArgs e) {
var contextMenu = (sender as ContextMenuStrip);
if (contextMenu != null) {
var sourceControl = contextMenu.SourceControl;
contextMenuStrip1.Items.Clear();
//contextMenuStrip1.Items.Add(...);
}
}