.NET Forum / Windows Forms / Design Time / May 2006
Custom Forms Designer Context Menus
|
|
Thread rating:  |
David Whitchurch-Bennett - 03 May 2006 14:33 GMT Hi,
Another question about Custom Forms Designers...
I would like to be able to add context menus to my controls, so if the user clicks on any of the controls on the design surface, a popup menu appears. I have tried to work with the MenuCommandService, and can add the verb and handler, but cannot seem to get the menu to appear when right-clicking.
Thanks in advance!
David.
Linda Liu [MSFT] - 04 May 2006 12:07 GMT Hi David,
Thank you for posting.
This is a quick note to let you know that I am performing research on this issue and will get back to you as soon as possible. I appreciate your patience.
Sincerely, Linda Liu Microsoft Online Community Support
==================================================== When responding to posts,please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ====================================================
G Himangi - 04 May 2006 12:19 GMT You can specify context menu verbs for your controls by associating a designer (derived from ControlDesigner) for your controls and overriding the Verbs property of the designer class.
G Himangi, Sky Software http://www.ssware.com *Shell MegaPack for ActiveX & .Net : Advanced Controls for Drop-In Windows Explorer GUI for your App. *EZNamespaceExtensions.Net : Rapid Development of Shell Namespace Extensions in .Net *EZShellExtensions.Net : Rapid Development of IE Toolbars, IE Explorer Bars & Windows Shell Extensions in .Net
> Hi, > [quoted text clipped - 10 lines] > > David. InK_ - 06 May 2006 09:05 GMT Hi David
If you want to have different items for different controls you should implement your spesialized designer for your controls and override Verbs property.
I had a task to do 'Send to back' and 'Bring to front' as global verbs.
I did this in the following way: 1) implemented System.ComponentModel.Design.IMenuCommandService 2)added it to the custom designerhost which implemented IServiceContainer serviceContainer.AddService(typeof(IMenuCommandService), new MenuCommandServiceImpl(this));
and then : //add global verbs IMenuCommandService menuCommandService = (IMenuCommandService)this.GetService(typeof(IMenuCommandService)); DesignerVerb verb = new DesignerVerb("Bring To Front", new EventHandler(this.BringToFrontClickHandler), StandardCommands.BringToFront); menuCommandService.AddVerb(verb); verb = new DesignerVerb("Send To Back", new EventHandler(this.SendToBackClickHandler), StandardCommands.SendToBack); menuCommandService.AddVerb(verb);
3)Handlers private void BringToFrontClickHandler(object sender, EventArgs e) { IMenuCommandService menuCommandService = (IMenuCommandService)this.GetService(typeof(IMenuCommandService)); menuCommandService.GlobalInvoke(StandardCommands.BringToFront); } private void SendToBackClickHandler(object sender, EventArgs e) { IMenuCommandService menuCommandService = (IMenuCommandService)this.GetService(typeof(IMenuCommandService)); menuCommandService.GlobalInvoke(StandardCommands.SendToBack); } 4) then I should hide the context menu on selectionService_SelectedChanged //hide opened context menu MenuCommandServiceImpl menuService = (MenuCommandServiceImpl)m_host.GetService(typeof(IMenuCommandService)); menuService.HideContextMenu(); Hope it will help somehow.
 Signature Regards, Inna Stetsyak aka InK_
> Hi, > [quoted text clipped - 8 lines] > > David. Linda Liu [MSFT] - 08 May 2006 08:10 GMT Hi David,
In order to get a menu to pop up when right-clicking on a custom design surface, you should do two things. The first thing is to create a class implementing the Interface IMenuCommandService and add the service to the custom design surface. The second thing is to add verbs to the MenuCommandService.
Suppose you have opened a form in the custom design surface and added several controls on the form. When you right-click on the controls or the form in your custom design surface, a context menu will appear.
If you want to have a different menu item when you right-click on a custom control in your custom design surface, you should associate a designer to your custom control and override the Verbs property of the designer class.
The following is a sample. // the definition of a class implementing IMenuCommandService public class MenuCommandServiceImpl : IMenuCommandService { // the host private IDesignerHost host; // commandId-command mapping private IDictionary commands; // menuItem-verb mapping private IDictionary menuItemVerb; // the global verbs collection. private DesignerVerbCollection globalVerbs; // we use the same context menu over-and-over private ContextMenu contextMenu; // we keep the lastSelectedComponent around private IComponent lastSelectedComponent;
public MenuCommandServiceImpl(IDesignerHost host) { this.host = host; commands = new Hashtable(); globalVerbs = new DesignerVerbCollection(); menuItemVerb = new Hashtable(); contextMenu = new ContextMenu(); lastSelectedComponent = null; } #region Implementation of IMenuCommandService
/// called to add a MenuCommand public void AddCommand(System.ComponentModel.Design.MenuCommand command) { if (command == null) { throw new ArgumentException("command"); } // don't add commands twice if (FindCommand(command.CommandID) == null) { commands.Add(command.CommandID, command); } }
/// called to remove a MenuCommand public void RemoveCommand(System.ComponentModel.Design.MenuCommand command) { if (command == null) { throw new ArgumentException("command"); } commands.Remove(command.CommandID); }
/// called when to add a global verb public void AddVerb(System.ComponentModel.Design.DesignerVerb verb) { if (verb == null) { throw new ArgumentException("verb"); } globalVerbs.Add(verb); // create a menu item for the verb and add it to the context menu MenuItem menuItem = new MenuItem(verb.Text); menuItem.Click += new EventHandler(MenuItemClickHandler); menuItemVerb.Add(menuItem, verb); contextMenu.MenuItems.Add(menuItem); } /// called to remove global verb public void RemoveVerb(System.ComponentModel.Design.DesignerVerb verb) { if (verb == null) { throw new ArgumentException("verb"); }
globalVerbs.Remove(verb);
// find the menu item associated with the verb MenuItem associatedMenuItem = null; foreach (DictionaryEntry de in menuItemVerb) { if (de.Value == verb) { associatedMenuItem = de.Key as MenuItem; break; } } // if we found the verb's menu item, remove it if (associatedMenuItem != null) { menuItemVerb.Remove(associatedMenuItem); } // remove the verb from the context menu too contextMenu.MenuItems.Remove(associatedMenuItem); } /// returns the MenuCommand associated with the commandId. public System.ComponentModel.Design.MenuCommand FindCommand(System.ComponentModel.Design.CommandID commandID) { return commands[commandID] as MenuCommand; }
/// called to invoke a command public bool GlobalInvoke(System.ComponentModel.Design.CommandID commandID) { bool result = false; MenuCommand command = FindCommand(commandID); if (command != null) { command.Invoke(); result = true; } return result; }
/// called to show the context menu for the selected component. public void ShowContextMenu(System.ComponentModel.Design.CommandID menuID, int x, int y) { ISelectionService selectionService = host.GetService(typeof(ISelectionService)) as ISelectionService; // get the primary component IComponent primarySelection = selectionService.PrimarySelection as IComponent; // if the he clicked on the same component again then just show the context // menu. otherwise, we have to throw away the previous // set of local menu items and create new ones for the newly // selected component if (lastSelectedComponent != primarySelection) { // remove all non-global menu items from the context menu ResetContextMenu(); // get the designer IDesigner designer = host.GetDesigner(primarySelection); // not all controls need a desinger if (designer != null) { // get designer's verbs DesignerVerbCollection verbs = designer.Verbs; foreach (DesignerVerb verb in verbs) { // add new menu items to the context menu CreateAndAddLocalVerb(verb); } } } // we only show designer context menus for controls if (primarySelection is Control) { Control comp = primarySelection as Control; Point pt = comp.PointToScreen(new Point(0, 0)); contextMenu.Show(comp, new Point(x - pt.X, y - pt.Y)); } // keep the selected component for next time lastSelectedComponent = primarySelection; }
/// returns the the current designer verbs public System.ComponentModel.Design.DesignerVerbCollection Verbs { get { // create a new collection DesignerVerbCollection availableVerbs = new DesignerVerbCollection(); // add the global verbs if (globalVerbs != null && globalVerbs.Count > 0) { availableVerbs.AddRange(globalVerbs); } // now add the local verbs ISelectionService selectionService = host.GetService(typeof(ISelectionService)) as ISelectionService; IComponent primaryComponent = selectionService.PrimarySelection as IComponent; if (primaryComponent != null) { IDesigner designer = host.GetDesigner(primaryComponent); if (designer != null && designer.Verbs != null && designer.Verbs.Count > 0) { availableVerbs.AddRange(designer.Verbs); } } return availableVerbs; } }
#endregion
// called to invoke menu item verbs private void MenuItemClickHandler(object sender, EventArgs e) { // get the menu item MenuItem menuItem = sender as MenuItem; if (menuItem != null) { // get and invoke the verb DesignerVerb verb = menuItemVerb[menuItem] as DesignerVerb; if (verb != null) { try { verb.Invoke(); } catch { } } } } // removes all local verbs from the context menu private void ResetContextMenu() { if (contextMenu != null && contextMenu.MenuItems != null && contextMenu.MenuItems.Count > 0) { MenuItem[] menuItemArray = new MenuItem[contextMenu.MenuItems.Count]; contextMenu.MenuItems.CopyTo(menuItemArray, 0); foreach (MenuItem menuItem in menuItemArray) { // if its not in the global list, remove it if (!IsInGlobalList(menuItem.Text)) { contextMenu.MenuItems.Remove(menuItem); } // get rid of the menu item from the mapping menuItemVerb.Remove(menuItem); } } } // creats and adds a local verb private void CreateAndAddLocalVerb(DesignerVerb verb) { if (verb == null) { throw new ArgumentException("verb"); } VerifyVerb(verb); // create a menu item for the verb MenuItem menuItem = new MenuItem(verb.Text); // attach the menu item click listener menuItem.Click += new EventHandler(MenuItemClickHandler); // do the menuItem-verb mapping menuItemVerb.Add(menuItem, verb); // add to context menu contextMenu.MenuItems.Add(menuItem); } // returns true if the verb is in the global verb collection private bool IsInGlobalList(string verbText) { bool found = false; if (globalVerbs != null && globalVerbs.Count > 0) { foreach (DesignerVerb dv in globalVerbs) { if (string.Compare(dv.Text, verbText, true) == 0) { found = true; break; } } } return found; } // we can't add the same verb twice private void VerifyVerb(DesignerVerb verb) { if (verb == null) { throw new ArgumentException("verb"); } // make sure the verb is not in the global list if (globalVerbs != null && globalVerbs.Count > 0) { foreach (DesignerVerb dv in globalVerbs) { if (string.Compare(dv.Text, verb.Text, true) == 0) { throw new Exception("Cannot add the same verb twice."); } } } // now check the menuItemVerb mapping if (menuItemVerb != null && menuItemVerb.Count > 0) { foreach (DesignerVerb dv in menuItemVerb.Values) { if (string.Compare(dv.Text, verb.Text, true) == 0) { throw new Exception("Cannot add the same verb twice."); } } } } }
// add the MenuCommandService to the class HostSurface public class HostSurface : DesignSurface { public HostSurface() : base() { IDesignerHost host = (IDesignerHost)this.GetService(typeof IDesignerHost)); this.AddService(typeof(IMenuCommandService), new MenuCommandServiceImpl(host)); // add verbs to the MenuCommandService in HostSurface class IMenuCommandService _menucommandService = (IMenuCommandService)this.GetService(typeof(IMenuCommandService)); DesignerVerb m_Verb = new DesignerVerb("First Designer Verb", new EventHandler(OnFirstItemSelected)); _menucommandService.AddVerb(m_Verb); m_Verb = new DesignerVerb("Second Designer Verb", new EventHandler(OnSecondItemSelected)); _menucommandService.AddVerb(m_Verb); } private void OnFirstItemSelected(object sender, EventArgs args) { // Display a message System.Windows.Forms.MessageBox.Show("The first designer verb was invoked."); }
private void OnSecondItemSelected(object sender, EventArgs args) { // Display a message System.Windows.Forms.MessageBox.Show("The second designer verb was invoked."); } }
Hope this is helpful to you. If you have any other concerns or need anything else, please feel free to let me know.
Sincerely, Linda Liu Microsoft Online Community Support
==================================================== When responding to posts,please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ====================================================
David Whitchurch-Bennett - 11 May 2006 16:49 GMT Hi Linda,
Sorry for the delay - been a bit sidetracked with other projects...
Your code worked fantastically, so thanks for that. Converted the implmentation to VB, so here it is in VB.Net for anyone else who may need some help with this problem (outlook express put all the extra spacing in!)
David
Imports System.ComponentModel Imports System.ComponentModel.Design
Public Class MenuCommandServiceImpl Implements IMenuCommandService
' the host
Private host As IDesignerHost
' commandId-command mapping
Private commands As IDictionary
' menuItem-verb mapping
Private menuItemVerb As IDictionary
' the global verbs collection.
Private globalVerbs As DesignerVerbCollection
' we use the same context menu over-and-over
Private contextMenu As ContextMenu
' we keep the lastSelectedComponent around
Private lastSelectedComponent As IComponent
Public Sub New(ByVal host As IDesignerHost)
Me.host = host
commands = New Hashtable()
globalVerbs = New DesignerVerbCollection()
menuItemVerb = New Hashtable()
contextMenu = New ContextMenu()
lastSelectedComponent = Nothing
End Sub
#Region "Implementation of IMenuCommandService"
'/ called to add a MenuCommand
Public Sub AddCommand(ByVal command As System.ComponentModel.Design.MenuCommand) Implements IMenuCommandService.AddCommand
If command Is Nothing Then
Throw New ArgumentException("command")
End If
' don't add commands twice
If FindCommand(command.CommandID) Is Nothing Then
commands.Add(command.CommandID, command)
End If
End Sub
'/ called to remove a MenuCommand
Public Sub RemoveCommand(ByVal command As System.ComponentModel.Design.MenuCommand) Implements IMenuCommandService.RemoveCommand
If command Is Nothing Then
Throw New ArgumentException("command")
End If
commands.Remove(command.CommandID)
End Sub
'/ called when to add a global verb
Public Sub AddVerb(ByVal verb As System.ComponentModel.Design.DesignerVerb) Implements IMenuCommandService.AddVerb
If verb Is Nothing Then
Throw New ArgumentException("verb")
End If
globalVerbs.Add(verb)
' create a menu item for the verb and add it to the context Menu
Dim menuItem As MenuItem = New MenuItem(verb.Text)
AddHandler menuItem.Click, AddressOf MenuItemClickHandler
menuItemVerb.Add(MenuItem, verb)
contextMenu.MenuItems.Add(MenuItem)
End Sub
'/ called to remove global verb
Public Sub RemoveVerb(ByVal verb As System.ComponentModel.Design.DesignerVerb) Implements IMenuCommandService.RemoveVerb
If verb Is Nothing Then
Throw New ArgumentException("verb")
End If
globalVerbs.Remove(verb)
' find the menu item associated with the verb
Dim associatedMenuItem As MenuItem = Nothing
Dim de As DictionaryEntry
For Each de In menuItemVerb
If de.Value Is verb Then
associatedMenuItem = de.Key
Exit For
End If
Next
' if we found the verb's menu item, remove it
If Not associatedMenuItem Is Nothing Then
menuItemVerb.Remove(associatedMenuItem)
End If
' remove the verb from the context menu too
contextMenu.MenuItems.Remove(associatedMenuItem)
End Sub
'/ returns the MenuCommand associated with the commandId.
Public Function FindCommand(ByVal commandID As CommandID) As MenuCommand Implements IMenuCommandService.FindCommand
Return commands(commandID)
End Function
'/ called to invoke a command
Public Function GlobalInvoke(ByVal commandID As System.ComponentModel.Design.CommandID) As Boolean Implements IMenuCommandService.GlobalInvoke
Dim result As Boolean = False
Dim command As MenuCommand = FindCommand(commandID)
If Not command Is Nothing Then
command.Invoke()
result = True
End If
Return result
End Function
'/ called to show the context menu for the selected component.
Public Sub ShowContextMenu(ByVal menuID As System.ComponentModel.Design.CommandID, ByVal x As Integer, ByVal y As Integer) Implements IMenuCommandService.ShowContextMenu
Dim selectionService As ISelectionService = host.GetService(GetType(ISelectionService))
' get the primary component
Dim primarySelection As IComponent = selectionService.PrimarySelection
' if the he clicked on the same component again then just show the(context)
' menu. otherwise, we have to throw away the previous
' set of local menu items and create new ones for the newly
' selected component
If lastSelectedComponent Is primarySelection Then
' remove all non-global menu items from the context menu
ResetContextMenu()
' get the designer
Dim designer As IDesigner = host.GetDesigner(primarySelection)
' not all controls need a desinger
If Not designer Is Nothing Then
' get designer's verbs
Dim verbs As DesignerVerbCollection = designer.Verbs
Dim verb As DesignerVerb
For Each verb In verbs
' add new menu items to the context menu
CreateAndAddLocalVerb(verb)
Next
End If
End If
' we only show designer context menus for controls
If TypeOf primarySelection Is Control Then
Dim comp As Control = primarySelection
Dim pt As Point = comp.PointToScreen(New Point(0, 0))
contextMenu.Show(comp, New Point(x - pt.X, y - pt.Y))
End If
' keep the selected component for next time
lastSelectedComponent = primarySelection
End Sub
'/ returns the the current designer verbs
Public ReadOnly Property Verbs() As System.ComponentModel.Design.DesignerVerbCollection Implements IMenuCommandService.Verbs
Get
' create a new collection
Dim availableVerbs As DesignerVerbCollection = New DesignerVerbCollection()
' add the global verbs
If globalVerbs Is Nothing Then
If globalVerbs.Count > 0 Then
availableVerbs.AddRange(globalVerbs)
End If
End If
' now add the local verbs
Dim selectionService As ISelectionService = host.GetService(GetType(ISelectionService))
Dim primaryComponent As IComponent = selectionService.PrimarySelection
If Not primaryComponent Is Nothing Then
Dim designer As IDesigner = host.GetDesigner(primaryComponent)
If Not designer Is Nothing Then
If designer.Verbs Is Nothing Then
availableVerbs.AddRange(designer.Verbs)
End If
End If
End If
Return availableVerbs
End Get
End Property
#End Region
' called to invoke menu item verbs
Private Sub MenuItemClickHandler(ByVal sender As Object, ByVal e As EventArgs)
' get the menu item
Dim menuItem As MenuItem = sender
If Not menuItem Is Nothing Then
' get and invoke the verb
Dim verb As DesignerVerb = menuItemVerb(menuItem)
If Not verb Is Nothing Then
Try
verb.Invoke()
Catch ex As Exception
' do nothing
End Try
End If
End If
End Sub
' removes all local verbs from the context menu
Private Sub ResetContextMenu()
If Not contextMenu Is Nothing Then
If contextMenu.MenuItems Is Nothing Then
Dim menuItemArray() As MenuItem = Nothing
contextMenu.MenuItems.CopyTo(menuItemArray, 0)
Dim menuItem As MenuItem
For Each menuItem In menuItemArray
' if its not in the global list, remove it
If Not IsInGlobalList(menuItem.Text) Then
contextMenu.MenuItems.Remove(menuItem)
End If
' get rid of the menu item from the mapping
menuItemVerb.Remove(menuItem)
Next
End If
End If
End Sub
' creats and adds a local verb
Private Sub CreateAndAddLocalVerb(ByVal verb As DesignerVerb)
If verb Is Nothing Then
Throw New ArgumentException("verb")
End If
VerifyVerb(verb)
' create a menu item for the verb
Dim menuItem As MenuItem = New MenuItem(verb.Text)
' attach the menu item click listener
AddHandler menuItem.Click, AddressOf MenuItemClickHandler
' do the menuItem-verb mapping
menuItemVerb.Add(MenuItem, verb)
' add to context menu
contextMenu.MenuItems.Add(MenuItem)
End Sub
' returns true if the verb is in the global verb collection
Private Function IsInGlobalList(ByVal verbText As String) As Boolean
Dim found As Boolean = False
If Not globalVerbs Is Nothing Then
If globalVerbs.Count > 0 Then
Dim dv As DesignerVerb
For Each dv In globalVerbs
If String.Compare(dv.Text, verbText, True) = 0 Then
found = True
Exit For
End If
Next
End If
End If
Return found
End Function
' we can't add the same verb twice
Private Sub VerifyVerb(ByVal verb As DesignerVerb)
If verb Is Nothing Then
Throw New ArgumentException("verb")
End If
' make sure the verb is not in the global list
If Not globalVerbs Is Nothing Then
If globalVerbs.Count > 0 Then
Dim dv As DesignerVerb
For Each dv In globalVerbs
If String.Compare(dv.Text, verb.Text, True) = 0 Then
Throw New Exception("Cannot add the same verb twice.")
End If
Next
End If
End If
' now check the menuItemVerb mapping
If Not menuItemVerb Is Nothing Then
If menuItemVerb.Count > 0 Then
Dim dv As DesignerVerb
For Each dv In menuItemVerb.Values
If String.Compare(dv.Text, verb.Text, True) = 0 Then
Throw New Exception("Cannot add the same verb twice.")
End If
Next
End If
End If
End Sub
End Class
'' add the MenuCommandService to the class HostSurface
Public Class HostSurface
Inherits DesignSurface
Public Sub New()
MyBase.New()
Dim host As IDesignerHost = Me.GetService(GetType(IDesignerHost))
host.AddService(GetType(IMenuCommandService), New MenuCommandServiceImpl(host))
' add verbs to the MenuCommandService in HostSurface class
Dim menucommandService As IMenuCommandService = GetService(GetType(IMenuCommandService))
Dim m_Verb As DesignerVerb = New DesignerVerb("First Designer Verb(", AddressOf OnFirstItemSelected)
menucommandService.AddVerb(m_Verb)
m_Verb = New DesignerVerb("Second Designer Verb", AddressOf OnSecondItemSelected)
menucommandService.AddVerb(m_Verb)
End Sub
Private Sub OnFirstItemSelected(ByVal sender As Object, ByVal arge As EventArgs)
' Display a message
System.Windows.Forms.MessageBox.Show("The first designer verb was invoked.")
End Sub
Private Sub OnSecondItemSelected(ByVal sender As Object, ByVal args As EventArgs)
' Display a message
System.Windows.Forms.MessageBox.Show("The second designer verb was invoked.")
End Sub
End Class
> Hi David, > [quoted text clipped - 366 lines] > from your issue. > ==================================================== Linda Liu [MSFT] - 12 May 2006 02:38 GMT Hi David,
Appreciate your update and response. I am glad to hear that the problem has been fixed. And you are so warm-hearted to convert the implementation to VB.
If you have any other questions or concerns, please do not hesitate to contact us. It's always our pleasure to be of assistance.
Have a nice day!
Sincerely, Linda Liu Microsoft Online Community Support
==================================================== When responding to posts,please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ====================================================
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|