.NET MenuItem.IsSubmenuOpen = true only works the first time - c#

I have a ContextMenu with some sub-menus that have items (MenuItem) that can be selected. When the ContextMenu is opened, I want to recursively open the currently selected item. So, I have the following code:
protected override void OnOpened( RoutedEventArgs e ) {
base.OnOpened( e );
OpenCurrentSubMenu( Items );
}
private static bool OpenCurrentSubMenu( ItemCollection itemCollection ) {
foreach (MenuItem item in itemCollection) {
if (item.IsChecked) {
return true;
}
else if( OpenCurrentSubMenu( item.Items ) ) {
item.IsSubmenuOpen = true;
return true;
}
}
return false;
}
I also have some other code that ensures that only one item is checked.
This seems to work great the first time I select an item in a sub-menu. When I re-open the ContextMenu, the open sub-menus cascade open to the selected item:
However, when I leave the context menu, and re-open it a second time, the selected menu does NOT open:
Does anyone know why and how to fix it?

Three things to try:
When the context menu is opened, recurse over the entire hierarchy and set IsSubmenuOpen = false before you try to open any submenus. It may be that the previously open submenu is remembered and thus you're trying to tell it to have two open submenus at the same level.
Recurse to find the submenus that need to be opened and store them in a list. Then iterate through the list and set them so that the topmost menu is set open before its child submenu is set open. (It may be that trying to open the child when its parent is not yet open won't always work reliably).
Nasty brute force approach: Delete and recreate the context menu each time it is opened. It's not nice, but if you're opening a context menu you're likely to be worried about the performance implications. And it appears that it works the first time, so make every time the first time.

It might help to set the value to false before setting it back to true. I can't confirm this though.
item.IsSubmenuOpen = false;
item.IsSubmenuOpen = true;

I think the logic should work well ... when the menu item is checked, but not when sub menu item is checked.
In my opinion try two loops .... one for menu and another one for sub menu items.

None of the solutions works/clear to me and none of them looks elegant.
I found something that works.
Try this:
public partial class WindowWithContextMenu : Window
{
// Add this Show method
public new void Show()
{
base.Show();
base.Activate();
// Do this for any context menu!
MyContextMenu.IsSubmenuOpen = true;
MyContextMenu.IsSubmenuOpen = false;
MyContextMenu2.IsSubmenuOpen = true;
MyContextMenu2.IsSubmenuOpen = false;
// And so on
}
...
...
}
This way you modify a bit the original Show() method of the window.
Now when you call the Show() method, it will run this small hack.
Then at some point, you may click on some button that opens the menus.
The thing is that this time it will always work and not only on the first time!
MyContextMenu and MyContextMenu2 are defined in the XAML file and they are not opened by default in the XAML file.
I have no idea why this works but it did. It is ugly but the hack is simple and a copy-paste solution...
I also found that in order to reproduce the issue, you need to lock the account and log in again. It reproduced by 100% each time I did it.

Related

Settings Save slow down app

I've a list of element with a checkbox. The items are available inside a Checkbox, and I save all the checked value inside my settings app, in particular, I've a field called available_countries and I save all the event inside this field.
Essentially when a item is checked a fire the event associated:
private void AvailableCountries_Checked(object sender, RoutedEventArgs e)
{
var item = sender as CheckBox;
if (item.Content.ToString() == "All")//first item of the list need to check all
{
Vm.AvailableNations.Select(c => { c.IsChecked = true; return c; }).ToList(); //Execute the IsChecked update through linq
}
//Check if the checked value is already added in the settings..
var nationsArr = Properties.Settings.Default.available_countries.Split(';');
foreach (var country in nationsArr)
{
if (country == item.Content.ToString()) //Nation already added!
{
return;
}
}
Properties.Settings.Default.available_countries += item.Content.ToString() + ";"; //Separator
Properties.Settings.Default.Save();
}
Now this code working well and is very simple, but I noticed that .Save() at the end slow down the app performance, infact when I click on the first item of the ComboBox that have as .Content : All I need to change all the IsChecked property of all element.
I need also to check if the value is already added in the Settings.
I noticed that if I remove the last line of code I doesn't see the delay time, about 1/2 seconds.
There is a way to improve it?
Well, the settings are stored in an XML file in the user's profile, so yes, whenever you save your settings, a short delay will be there. No way to change that.
A possible solution in your case would be to not save the settings when the check box is checked, but in some other event (like for example an OK button of the settings dialog, etc).
Another solution would be to start a background task that changes the values and saves the settings, but you'd have to make sure this is properly synchronized.
A third solution would be to just change the settings object, but not save until the application is closed. That would slow down the process of closing the application, but ...

How to programmatically enable \ disable nested sub-menu items in a ToolStripMenuItem?

In my Winforms application I have a ToolStripMenuItem with nested sub-items, the structure of which is shown below.
File
.+...Add As....+.....File
............................Folder
............................Root Folder
Under 'Add As' I want to be able to programmatically enable and disable 'File', 'Folder', and 'Root Folder' as required. How can I access these nested items in code?
I have tried ToolStripMenuItem.DropDownItems[0].Enabled = true\false; but this affects 'Add As' and everything below it in the menu hiearachy.
If I use an index greater than zero in the code above I get an 'index out of range' error. How do I go about achieving this functionality?
Simply reference the sub-items by their own names eg:
FileToolStripMenuItem.Enabled = false;
FolderToolStripMenuItem.Enabled = false;
RootFolderToolStripMenuItem.Enabled = false;
Unless I'm missing something, this seems like the simplest answer.
As Hans' hinted in his comment, you are referencing the wrong DropDownItems collection.
To do this using indexes will get ugly quickly.
It's simpler to just reference the parent menu and loop through "its" menu collection:
private void toggleMenu_Click(object sender, EventArgs e) {
foreach (ToolStripMenuItem toolItem in addAsToolStripMenuItem.DropDownItems) {
toolItem.Enabled = !toolItem.Enabled;
}
}
Here is the ugly method, which would be difficult to maintain if you decided later to rearrange your menu structure:
foreach (ToolStripMenuItem toolItem in ((ToolStripMenuItem)((ToolStripMenuItem)menuStrip1.Items[0]).DropDownItems[0]).DropDownItems) {
toolItem.Enabled = !toolItem.Enabled;
}

How to disable ToolStripMenuItem in Context menu Dynamically?

In my windows application i have a context menu with a grid the problem is that I want to disable the ToolStripMenuItem in context menu according to the user previlages.How can i do that. i have done like this but it is not working
private void contextMenuStrip_Machine_Opening(object sender, CancelEventArgs e)
{
toolStripAuthorize.Enabled = INFOpermission.accessAuthorize;
}
but it is not working
You need to set toolStripAuthorize.Enabled to either true or false.
I have no idea what INFOpermission.accessAuthorize is because you didn't show the code that defines that (enum?), but if it's anything other than false, this isn't going to work out like you expect.
I can guarantee that setting the Enabled property of the ToolStripMenuItem that you want to disable to false in the Opening event handler will work. If it's not working for you, you're doing something else wrong, and you need to give us some more information to go on.
If you're stuck, see the sample code here: How to: Handle the ContextMenuStrip Opening Event
EDIT: Armed with new information provided in the comments, I've now isolated the source of the problem. You've assigned the ContextMenuStrip to the RowTemplate of a DataGridView control, and are therefore not able to modify items contained in that context menu in its Opening event handler method.
It turns out that this is a known bug that someone decided was "by design". You can see the original bug report here on Microsoft Connect. The explanation given is that whenever a new row is created based on the RowTemplate (which is how the RowTemplate works), the ContextMenuStrip that you've assigned gets cloned as well. That means the same context menu instance is not used for each row, and whatever properties that you try to set on the original menu items have no effect.
Fortunately, it also gives us a workaround. Like all events, the Opening event passes the actual instance of the ContextMenuStrip that is about to be opened as its sender parameter. This is the context menu whose items you need to modify in order for your alterations to be visible.
So what's the code? It looks like this:
private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
{
ContextMenuStrip cmnu = (ContextMenuStrip)sender;
cmnu.Items[1].Enabled = false;
}
Notice, though, that you'll have to reference the individual menu item that you want to modify by its index. This is just the zero-based position of the item in the menu that you want to modify. You can't use the toolStripAuthorize object like you were trying to do before because a new instance of it has been cloned for each new context menu instance.

ContextMenuStrip On RightClick, IF Items Are Selected In Listview?

I have a ContextMenuStrip attached to a list view, and it's working great, but what I'm interested in knowing is how I can have it show up only when one or more items are selected in the listview.
Thanks!
You could use the Opening event. The event args has a Cancel property so that you can examine the state of your application and decide whether to have the menu show (by doing nothing) or prevent it from showing (by setting e.Cancel = true). However, like #Grzenio mentions, I would find it more intuitive if the item that i right-clicked on became selected automatically.
Another option would be to use the Opening event to populate the context menu with only one disabled item, with a text like (no item is selected) or so; this would inform the user about why the command is not available.
For other people reading this thread, a nice way is to gray out the options in the menu (in Opening event) when no items are selected instead of not displaying the menu at all
if (List.SelectedItems.Count == 0)
{
// e.Cancel=true;
List.Enabled = false;
}
else
{
List.Enabled = true;
}
For me its intuitive that if you have no items selected (or you right-click on a non-selected item), the item would get automatically selected just before you show the context menu.
If the first solution is not acceptable, I think I would try to attach the ContextMenuStrip when items get selected and detach it when they are unselected.
Private Sub ListView1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles ListView1.MouseUp
If e.Button = MouseButtons.Right And ListView1.SelectedItems.Count > 0 Then
Dim cn As New ContextMenuStrip()
cn.Items.Add("Apple")
Me.ListView1.ContextMenuStrip = cn
cn.Show(Control.MousePosition.X, Control.MousePosition.Y)
End If
End Sub

Dynamically Created User Controls In C#

I am working in a C# winforms project and I have a user control which gets loaded upon its selection from a tool-strip menu. I have a dictionary lookup set to occur upon form load of the user control for other functionality. Also, when I close the user control I am just using the ".Hide();" method. I noticed that when I load the user control the first time everything is fine, but when I close it and choose to open it again the 2nd time it creates a new instance of the object thus throwing off my dictionary lookup. Therefore, I wrote some code in an attempt to fix the issue.
What I need to do is to somehow say that if an instance of the user control already exists, do not create a new instance of that object. Instead, just make the user control visible again. Therefore I have written code in an attempt to accomplish this purpose. When I select the item the first time, everything is fine. When I hide the user control and try to re-open it again nothing happens.
The following is the code I have written for this purpose which occurs upon the selection of the item from the tool-strip menu:
if (Controls.ContainsKey("CheckAvailUserControl"))
{
Controls["CheckAvailUserControl"].Dock = DockStyle.Fill;
Controls["CheckAvailUserControl"].Visible = true;
Controls["CheckAvailUserControl"].Show();
Controls["CheckAvailUserControl"].Refresh();
}
else
{
UserControl checkAvailUserControlLoad = new CheckAvailUserControl();
Controls.Add(checkAvailUserControlLoad);
checkAvailUserControlLoad.Dock = DockStyle.Fill;
checkAvailUserControlLoad.Visible = true;
checkAvailUserControlLoad.Show();
}
When I trace through my code in the debugger it is in fact hitting the right parts of the above if/else statement. Its just not displaying the user control on the screen the 2nd time I attempt to load it.
The question is: How do I get the user control to load correctly after I close it, then select it from the tool-strip menu again?
I think that Controls.ContainsKey(...) is always returning false, because you never assigned a name to your control when you created it.
If, when you create the control, you say
//...
checkAvailUserControlLoad.Name = "Something"
//...
Controls.Add(checkAvailUserControlLoad);
then
Controls.ContainsKey("Something")
will return true, and you'll be able to re-use the control by using Controls["Something"]
Here you go:
private void button_Click(object sender, EventArgs e)
{
// pass in the containing panel
LoadControl<MyControls.MyControl>(panelContainer);
}
void LoadControl<T>(Panel panel) where T : Control, new()
{
T _Control = GetControl<T>(panel);
if (_Control == null)
{
_Control = new T();
_Control.Dock = DockStyle.Fill;
panel.Controls.Add(_Control);
}
_Control.BringToFront();
}
T GetControl<T>(Panel panel) where T : Control
{
Type _Type = typeof(T);
String _Name = _Type.ToString();
if (!panel.Controls.ContainsKey(_Name))
return null;
T _Control = panel.Controls[_Name] as T;
return _Control;
}
This could work, but I think it's a little bit backwards: you're throwing new code at a problem that could be solved instead by moving your old code.
Instead, think about how the events work in your form. I bet that if you move your creating code to a slightly different event, or detect when the event is fired later and ignore those, you could fix the problem in a much nicer way.

Categories

Resources