I have a form which contains tab panel with many tap pages. Each of them has its own context menu (display on right-click). But If I add a ToolStripMenuItem to multiple ContextMenuStrips only last menu strip really has this menu item.
Simple code example is:
ToolStripMenuItem tim_refresh = new ToolStripMenuItem("Refresh", null, (o, e) =>
{
MessageBox.Show("Refresh");
});
ContextMenuStrip cms1 = new ContextMenuStrip();
cms1.Items.Add(tim_refresh);
ContextMenuStrip cms2 = new ContextMenuStrip();
cms2.Items.Add(tim_refresh);
this.tblDataManagerObjects.ContextMenuStrip = cms1;
this.tblDataSourceTypes.ContextMenuStrip = cms2;
If one shows this menus one by one, first will be empty...How can I achieve what I want?
Thi is because visual can not be child of multiple diferent visuals in the same time. In your case tim_refresh is a child of cms1 and cms2 at the same time.
You need to create two completely separate instances of ToolStripMenuItem.
EDIT:
You can extract visual creation in factor method to simplify multiple objects instantiation:
private ToolStripMenuItem CreateToolStripMenuItem(string name)
{
return new ToolStripMenuItem(name, null, (o, e) =>
{
MessageBox.Show(name);
});
}
// then just call it once per each menu strip
ContextMenuStrip cms1 = new ContextMenuStrip();
cms1.Items.Add(CreateToolStripMenuItem("Refresh"));
ContextMenuStrip cms2 = new ContextMenuStrip();
cms2.Items.Add(CreateToolStripMenuItem("Refresh"));
one context menu is displayed once at a time; you probably don't need many clones everywhere, but you may want to move one single instance of your menu items to the menu menu strip at the moment menu strip is opening;
I'm moving here the named (grand) parent's items to the child (currently opening) menu when the local copy is empty, and all the next ctx openings I just AddRange, which moves the "located" three menu items from the previously opened ctxMenuStrip to the currently-opening-one:
// http://stackoverflow.com/questions/8307959/toolstripmenuitem-for-multiple-contextmenustrip?rq=1
// http://stackoverflow.com/questions/6275120/toolstripmenuitem-added-to-several-places?rq=1
// WILL_ADD_PARENT_MENU_ITEMS_IN_Opening first time opened we locate common menu items from GrandParent, then we move them to the current slider; cool?
private static ToolStripItem[] separatorLoadSave = null;
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) {
if (separatorLoadSave == null) {
separatorLoadSave = new ToolStripItem[3];
Control slidersAutoGrow = base.Parent;
if (base.Parent.Name != "SlidersAutoGrow") return;
Control slidersForm = slidersAutoGrow.Parent;
if (slidersForm.Name != "SlidersForm") return;
ToolStripItem[] separator = slidersForm.ContextMenuStrip.Items.Find("toolStripSeparator1", false);
if (separator.Length > 0) separatorLoadSave[0] = separator[0];
ToolStripItem[] load = slidersForm.ContextMenuStrip.Items.Find("mniParameterSetLoad", false);
if (load.Length > 0) separatorLoadSave[1] = load[0];
ToolStripItem[] save = slidersForm.ContextMenuStrip.Items.Find("mniParameterSetSave", false);
if (save.Length > 0) separatorLoadSave[2] = save[0];
}
this.contextMenuStrip1.SuspendLayout();
this.contextMenuStrip1.Items.AddRange(separatorLoadSave);
this.contextMenuStrip1.ResumeLayout();
}
Related
I'm currently stuck with this problem: my project communicates via the TR.064 interface with the Fritz!Box (router) to get information about the user in my LAN.
This action is permanently done in the background using a BackgroundWorker.
The BW calls an object of the Communication class to gain the list of active hosts. A second list contains all user, that are on a certain watch-list. Users on this watchlist are highlighted.
Then, for every user a ToolStripMenuItem is created that displays the hostname and its IP address.
All items are added to a temporary static class variable, to pass it to the bw-RunCompleted method.
This method now clears the ContextMenuStrip of the NotifyIcon and adds a "starter menu" that has the standard buttons like quit, settings, help, etc.
After that, all items of the temporary class variable are then added to the ContextMenuStrip.
But while doing this, the list of the ToolStripItems of the temporary contextmenu are deleted after assigning them to the 'main'-ContextMenuStrip which causes an exception.
For example:
The bw gets a list of 8 hosts from the router. It then stores them correctly in the temporary menu. The temporary menu has now 8 items.
When I loop through the temporary menu to assign every item to the 'main' menu, this item gets deleted in the temporary list:
temp | main | items copied
8 | 0 | 0
7 | 1 | 1
--> throws Exception because list has changed (see picture)
private static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
watchBoxCommunicator.askFritzBoxForHosts();
// wait until Communicator finished running
while (watchBoxCommunicator._isRunning) ;
// now create a new menu if needed
// --> only create new menu, if there are new hosts or hosts that went offline
if (watchBoxCommunicator.hosts_on.Count > 0 || watchBoxCommunicator.hosts_off.Count > 0)
{
var tempMenu = new ContextMenuStrip();
//tempMenu.Dock = DockStyle.Bottom;
// first sort the elements in the onlineList
// only sort the watchlist hosts, when there are objects in the list
// the online user list must at least contain one user as the computer running this code is online
List<GetGenericHostEntryResponse> tempListNonWatchlist = watchBoxCommunicator.onlineHosts.OrderBy(hostObject => hostObject.NewHostName).ToList();
List<GetGenericHostEntryResponse> tempListWatchlist = new List<GetGenericHostEntryResponse>();
try
{
tempListWatchlist = watchBoxCommunicator.hostsOnWatchlist.OrderBy(hostObject => hostObject.NewHostName).ToList();
}
catch (ArgumentNullException)
{
// do nothing
tempListWatchlist = new List<GetGenericHostEntryResponse>();
}
finally
{
// back-associate the hosts to the watchBoxCommunicator object to grant access to the sorted lists by other methods
watchBoxCommunicator.onlineHosts = tempListNonWatchlist;
watchBoxCommunicator.hostsOnWatchlist = tempListWatchlist;
}
// add only the hosts that are not on the watchlist
foreach (var host in watchBoxCommunicator.onlineHosts)
{
if (watchBoxCommunicator.hostsOnWatchlist.Contains(host)) continue;
var menuItem = new ToolStripMenuItem();
menuItem.Text = host.NewHostName + " (" + host.NewIPAddress + ")";
menuItem.Enabled = false;
if (host.NewInterfaceType.Equals("Ethernet"))
menuItem.Image = Properties.Resources.network_cable_64px;
else menuItem.Image = Properties.Resources.wi_fi_logo_64px;
tempMenu.Items.Add(menuItem);
}
// add all hosts that are on the watchlist to display them at the top of the menu
foreach (var host in watchBoxCommunicator.hostsOnWatchlist)
{
var menuItem = new ToolStripMenuItem();
menuItem.Text = host.NewHostName + " (" + host.NewIPAddress + ")";
if (host.NewInterfaceType.Equals("Ethernet"))
menuItem.Image = Properties.Resources.network_cable_64px;
else menuItem.Image = Properties.Resources.wi_fi_logo_64px;
menuItem.BackColor = Color.FromArgb(255, 255, 0);
tempMenu.Items.Add(menuItem);
}
// pass the menu to the RunCompleted to update the NotifyIcon's contextmenu
argumentMenu = tempMenu;
}
else
{
argumentMenu = new ContextMenuStrip();
}
}
private static void bw_RunCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
// if the argument's type is a contextmenustrip, update the contextmenu
// otherwise, jump to last line and restart backgroundworker
if (argumentMenu.Items.Count > 0)
{
// interrupt layout updater
wb_menu.SuspendLayout();
wb_menu.Items.Clear();
wb_menu.Items.AddRange(starterMenu.Items);
// add <argumentMenu> item-list to the item-list of <wb_menu>
//wb_menu.Items.AddRange(argumentMenu.Items);
ToolStripItemCollection itemC = argumentMenu.Items;
foreach (var item in itemC)
{
var itemN = item as ToolStripMenuItem;
wb_menu.Items.Add(itemN);
}
// update the <wb_Icon> icon
if (watchBoxCommunicator.hostsOnWatchlist.Count == 0)
{
wb_Icon.Icon = Properties.Resources.WatchBoxFullGreen;
}
else
{
wb_Icon.Icon = Properties.Resources.WatchBoxFullRed;
}
// continue layout updater
wb_menu.ResumeLayout();
}
// start the background-worker again
bw.RunWorkerAsync();
}
I really don't know why the list changes when I add the items to another list. Also the list is not touched or modified or read by any other thread at any time while 'copying' the items.
I am trying to reproduce the operation of the Control Expander WPF, or as shown in the menu of Outlook, Vertical Web Menu etc., since in WindowsForms this control does not exist. Here I leave the sample code: Menu_Expader.zip link GoogleDrive.
I have managed to do it using the following controls:
Panels
FlowLayoutPanel
1 Time Control
Button Vectors
Labels Vectors ...
This works perfectly, but it happens that to each panel I must establish a
Maximum Size and Minimum Size therefore every time I add an item inside I must modify the size of the panel where I add it, and the item are very close to each other is a bit annoying for the user's vision.
Example this is what I currently have:
EDIT
Code Sample:
// The state of an expanding or collapsing panel.
private enum ExpandState
{
Expanded,
Expanding,
Collapsing,
Collapsed,
}
// The expanding panels' current states.
private ExpandState[] ExpandStates;
// The Panels to expand and collapse.
private Panel[] ExpandPanels;
// The expand/collapse buttons.
private Button[] ExpandButtons;
// Initialize.
private void Form1_Load(object sender, EventArgs e)
{
// Initialize the arrays.
ExpandStates = new ExpandState[]
{
ExpandState.Expanded,
ExpandState.Expanded,
ExpandState.Expanded,
};
ExpandPanels = new Panel[]
{
panModule1,
panModule2,
panModule3,
};
ExpandButtons = new Button[]
{
btnExpand1,
btnExpand2,
btnExpand3,
};
// Set expander button Tag properties to give indexes
// into these arrays and display expanded images.
for (int i = 0; i < ExpandButtons.Length; i++)
{
ExpandButtons[i].Tag = i;
ExpandButtons[i].Image = Properties.Resources.expander_down;
}
}
// Start expanding.
private void btnExpander_Click(object sender, EventArgs e)
{
// Get the button.
Button btn = sender as Button;
int index = (int)btn.Tag;
// Get this panel's current expand
// state and set its new state.
ExpandState old_state = ExpandStates[index];
if ((old_state == ExpandState.Collapsed) ||
(old_state == ExpandState.Collapsing))
{
// Was collapsed/collapsing. Start expanding.
ExpandStates[index] = ExpandState.Expanding;
ExpandButtons[index].Image = Properties.Resources.expander_up;
}
else
{
// Was expanded/expanding. Start collapsing.
ExpandStates[index] = ExpandState.Collapsing;
ExpandButtons[index].Image = Properties.Resources.expander_down;
}
// Make sure the timer is enabled.
tmrExpand.Enabled = true;
}
// The number of pixels expanded per timer Tick.
private const int ExpansionPerTick = 7;
// Expand or collapse any panels that need it.
private void tmrExpand_Tick(object sender, EventArgs e)
{
// Determines whether we need more adjustments.
bool not_done = false;
for (int i = 0; i < ExpandPanels.Length; i++)
{
// See if this panel needs adjustment.
if (ExpandStates[i] == ExpandState.Expanding)
{
// Expand.
Panel pan = ExpandPanels[i];
int new_height = pan.Height + ExpansionPerTick;
if (new_height >= pan.MaximumSize.Height)
{
// This one is done.
new_height = pan.MaximumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
else if (ExpandStates[i] == ExpandState.Collapsing)
{
// Collapse.
Panel pan = ExpandPanels[i];
int new_height = pan.Height - ExpansionPerTick;
if (new_height <= pan.MinimumSize.Height)
{
// This one is done.
new_height = pan.MinimumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
}
// If we are done, disable the timer.
tmrExpand.Enabled = not_done;
}
I want to get a result similar to this - Bootstrap Menu Accordion:
Imitate that operation panels expand according to the quantity of item that it contains as long as it does not protrude from the screen, in which case it will show the scroll bar. I know there are software that provide custom controls like DVexpress, DotNetBar Suite among others, but they are Licensed Software I do not want to use it illegally pirate. Can you help me optimize it or create it in another way?
Environment: Visual Studio 2010 & .NET NetFramework 4.
The original question I made it in StackOverFlow in Spanish.
Modulo (Module)
Menu Principal (Main menu)
Mantenimientos (Maintenance)
Procesos (Processes)
Consultas (Queries)
Reportes (Reports)
Note: If someone speaks Spanish and English and can do a better translation, please edit the question. (Excuse the advertising on the image, I recorded the screen with a software trial version).
So I am creating a treeview selector with C#/GTKSharp. I have the basic tree view selector functionality working: The data is loaded into my model and I can click on a node to collapse/expand.
The part I can't work out is how to tell the cell renderer to display the collapse/expand toggle button. In the examples it appears as a triangle that points right or down depending on whether the node is opened or collapsed. I just have a blank space that works as expected as I click but shows nothing.
One possibility is that I have a white on white text issue but I doubt it as my labels show up fine and I have not done any formatting yet.
I tried adding code for ShowExpanders but that was already true.
TreeView = new Gtk.TreeView();
// We add the event handlers (i.e. the control part) to the tree
TreeView.RowActivated += SelectorActivated; //On double click
TreeView.Selection.Changed += SelectorSelected; // On select (single click)
// Raise a context menu here??
// Connect to the ButtonPressEvent
// Raise a popup button
// Create columns [View]
Gtk.TreeViewColumn TreeViewColumTitle = new Gtk.TreeViewColumn();
TreeViewColumTitle.Title = "Profile";
Gtk.CellRendererText NameCellTitle = new Gtk.CellRendererText();
TreeViewColumTitle.PackStart(NameCellTitle, true);
TreeViewColumTitle.SetCellDataFunc(NameCellTitle, new Gtk.TreeCellDataFunc(RenderTitle));
NameCellTitle.Mode = CellRendererMode.Activatable;
// Populate the model
// Note that we could dispense with this step if we generated an ITreeModel
// interface in the Object class.
BindModel(Model);
// Attach everything to the pane
TreeView.Model = GTKModel;
TreeView.AppendColumn(TreeViewColumTitle);
TreeView.ShowExpanders = true;
TreeView.ExpanderColumn.Visible = true;
...
private void BindModel(Model Model) {
GTKModel = new Gtk.TreeStore(typeof(Object));
foreach (Object Object in Model.Selector) {
var BindingData = new BindingDataGTK(this, Object);
BindingData.Iter = GTKModel.AppendValues(Object);
Object.BindingData = BindingData;
BindChildren(GTKModel, BindingData);
}
}
private void BindChildren(TreeStore TreeStore, BindingDataGTK ObjectBinding) {
foreach (var Child in ObjectBinding.Object) {
var BindingData = new BindingDataGTK(this, Child);
BindingData.Iter = TreeStore.AppendValues(ObjectBinding.Iter, Child);
Child.BindingData = BindingData;
BindChildren(TreeStore, BindingData);
}
}
private void RenderTitle(Gtk.TreeViewColumn Column, Gtk.CellRenderer Cell,
Gtk.ITreeModel GTKModel, Gtk.TreeIter Iter) {
Object Object = (Object)GTKModel.GetValue(Iter, 0);
(Cell as Gtk.CellRendererText).Text = Object.Title;
Console.WriteLine("Render {0}", Object.Title);
}
So far as I know this is pretty much an automatic feature, I don't think anything special is needed to make it happen (I've certainly never needed to). You might want to try using a TreeIter to construct your tree instead?
E.g. assuming you already have a TreeView on your form with 0 (zero) columns in it called "treeview" and a list of "MyObject"s called "myListOfObjects"...
treeview.AppendColumn ("Some Title", new CellRendererText(), "text", 0);
Gtk.TreeStore _ts = new TreeStore (typeof(string));
foreach (IMyObject _mo in myListOfObjects) {
Gtk.TreeIter _it = _ts.AppendValues (_mo.SomeText);
RecurseInto (_ts, _it, _mo);
}
treeview.Model = _ts;
...
void RescureInto(Gtk.TreeStore ts, Gtk.TreeIter it, IMyObject mo)
{
foreach (IMyObject _child_mo in mo.Children) {
Gtk.TreeIter _it = ts.AppendValues (it, _child_mo.SomeText);
RecurseInto (ts, _it, _child_mo);
}
}
In theory this should work fine.
I am trying to find how I can add items to devExpress PopupMenu. I have tried the following:
manager = new BarManager();
listBoxMenu = new PopupMenu(manager);
listBoxMenu.ItemLinks.Add(manager.Items["Remove item"]);
listBoxMenu.ItemLinks.Add(manager.Items["Clear items"]);
As shown here http://documentation.devexpress.com/#WindowsForms/CustomDocument5472 (at the bottom), but it gives me an error saying the item is not initialized.
What is the proper way to add items? I can't find it anywhere.
Edit, here is how I did it:
//Creates the popup menu to be used for the keywords listbox
manager = new BarManager();
listBoxMenu = new PopupMenu(manager);
item = new BarButtonItem(manager, "Copy");
item2 = new BarButtonItem(manager, "Clear Item");
item3 = new BarButtonItem(manager, "Clear All Items");
listBoxMenu.ItemLinks.Add(item);
listBoxMenu.ItemLinks.Add(item2);
listBoxMenu.ItemLinks.Add(item3);
//Adds the seperator on the second item
item2.Links[0].BeginGroup = true;
manager.ItemClick += manager_ItemClick;
Check this code snippet and implement using the same way.
//create popup and manage objects
private DevExpress.XtraBars.BarManager barManager1;
private DevExpress.XtraBars.PopupMenu buttonContextMenu;
DevExpress.XtraBars.BarButtonItem menuButtonExport = new DevExpress.XtraBars.BarButtonItem();
DevExpress.XtraBars.BarButtonItem menuButtonSave = new DevExpress.XtraBars.BarButtonItem();
public TestForm8()
{
InitializeComponent();
barManager1 = new BarManager();
this.barManager1.Form = this;
buttonContextMenu = new DevExpress.XtraBars.PopupMenu(barManager1);
this.buttonContextMenu.Name = "subViewContextMenu";
menuButtonExport.Caption = "E&xport";
menuButtonExport.Id = 1;
menuButtonExport.Name = "menuButtonExport";
menuButtonExport.ItemClick += new ItemClickEventHandler(menuButtonExport_ItemClick);
menuButtonSave.Caption = "S&ave";
menuButtonSave.Id = 2;
menuButtonSave.Name = "menuButtonSave";
menuButtonSave.ItemClick += new ItemClickEventHandler(menuButtonSave_ItemClick);
//add items to barmanager
this.barManager1.Items.AddRange(new DevExpress.XtraBars.BarItem[] {
menuButtonExport,
menuButtonSave
});
//create links between bar items and popup
buttonContextMenu.ItemLinks.Add(barManager1.Items["menuButtonExport"]);
buttonContextMenu.ItemLinks.Add(barManager1.Items["menuButtonSave"]);
//finally set the context menu to the control or use the showpopup method on right click of control
barManager1.SetPopupContextMenu(btnInsert, buttonContextMenu);
}
Ref by step to include popup:
How to: Create a popup menu
How to: Add items to a container bar item (menu)
Populating Popup Menus
BarManager.SetPopupContextMenu Method
Your manager is empty:
manager = new BarManager();
The example you linked to is using a BarManager that was already created: barManager1, which I assume was created in the designer and populated with items.
From their BarManager help page:
After a BarManager has been added to a form/user control, you can create bars and bar commands using context menus right on the form, using the bar manager's Customization Window or its Designer. Please see the Toolbars Customization section, to learn more.
Goal:
Right clicking in the listview and choose different option.
Problem:
There are two problem:
*When I'm right clicking, the left corner of the menu is not exactly located in the arrow's spot location.
*How do I create a line in the menu?
The main problem about the menu
Need support to create these two redmark.
private void lstV_Stock_MouseUp(object sender, MouseEventArgs e)
{
switch (e.Button)
{
// Right mouse click
case MouseButtons.Right:
ContextMenu myContextMenu = new ContextMenu();
MenuItem menuItem1 = new MenuItem("New product");
MenuItem menuItem2 = new MenuItem("Delete");
MenuItem menuItem3 = new MenuItem("Add quantity");
// Clear all previously added MenuItems.
myContextMenu.MenuItems.Clear();
myContextMenu.MenuItems.Add(menuItem1);
myContextMenu.MenuItems.Add(menuItem2);
myContextMenu.MenuItems.Add(menuItem3);
if (lstV_Stock.SelectedItems.Count > 0)
{
foreach (ListViewItem item in lstV_Stock.SelectedItems)
{
myContextMenu.MenuItems[1].Visible = true;
myContextMenu.MenuItems[2].Visible = true;
myContextMenu.MenuItems[0].Visible = false;
}
}
else
{
myContextMenu.MenuItems[1].Visible = false;
myContextMenu.MenuItems[2].Visible = false;
myContextMenu.MenuItems[0].Visible = true;
}
myContextMenu.Show(lstV_Stock, this.PointToClient(Cursor.Position), LeftRightAlignment.Right);
menuItem1.Click += new System.EventHandler(this.menuItem1_Click);
break;
}
For the positioning, you can replace your
myContextMenu.Show(lstV_Stock, this.PointToClient(Cursor.Position), LeftRightAlignment.Right);
to
myContextMenu.Show(lstV_Stock, e.Location(), LeftRightAlignment.Right);
or the point e.X,e.Y. Not from this.PointToClient, but from the MouseEventArgs generating the event. You can check wahat MouseEvent have here.
To create a "line" you have to create a MenuItem with text "-"
Problem
If you just set the ListView.ContextMenu property and remove all your own right-click code, the menu should show up correctly.
For the line you need a ToolStripSeparator item. The designer will create one when you type '-' as the Text. You can drag them in the designer.
So, using a ContextMenu is the way to go here. Those "Lines" you're referring to are called Separaters.
If you're creating the COntext Menu in Design View, then click the Context Menu, then right-click inside the menu, and click Insert > Separater.
You can then drag it up or down, or into a sub-menu if you wish.