Find control that caused ContextMenuStrip menu to be shown - winforms

I've read a few articles on SO:
How to detrmine the control that cause ContextMenuStrip
Getting the control of a context menu
and a couple others that suggested use of the SourceControl property.. but none work in this context:
I have a ContextMenuStrip that has a child ToolStripMenuItem - this code from the windows forms designer generated section:
// _tileContextMenuStrip
//
this._tileContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tileKindToolStripMenuItem,
this.forceWidthScalingToolStripMenuItem,
this.forceHeightScalingToolStripMenuItem});
this._tileContextMenuStrip.Name = "_tileContextMenuStrip";
this._tileContextMenuStrip.Size = new System.Drawing.Size(184, 70);
//
// tileKindToolStripMenuItem
//
this.tileKindToolStripMenuItem.Name = "tileKindToolStripMenuItem";
this.tileKindToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.tileKindToolStripMenuItem.Text = "Tile Kind";
So the context menu strip and the menu item first in the list are fixed at design time. At runtime, the TSMI has child TSMIs added to it in a loop based on an enum:
foreach(TileKind t in typeof(TileKind).GetEnumValues()) {
ToolStripMenuItem tsmi = new ToolStripMenuItem(t.ToString("g"));
tsmi.Tag = t;
tsmi.Click += tsmi_Click;
tileKindToolStripMenuItem.DropDownItems.Add(tsmi);
}
Later I have 20 checkboxes on my form and I set their .ContextMenuStrip to be the same thing:
foreach(Thing t in someDataSource){
CheckBox c = new CheckBox();
c.Text = t.SomeData;
c.ContextMenuStrip = this._tileContextMenuStrip;
myPanelBlah.Controls.Add(c);
}
Great, so now I have all my checkboxes and they all show the context menu when I right click them, but when I choose one the sub-menu items, I just can't find out the control that fired the context menu...
//this the click handler for all the menu items dynamically added
void tsmi_Click(object sender, EventArgs e)
{
ToolStripMenuItem tsmi = sender as ToolStripMenuItem;
(tsmi.OwnerItem //the parent node in the menu tree hierarchy
.Owner as ContextMenuStrip) //it's a ContextMenuStrip so the cast succeeds
.SourceControl //it's always null :(
}
I can reliably get ahold of the contextmenustrip either by routing up from the event handler sender, or even just by referencing the ContextMenuStrip itself as a form instance variable, but SourceControl is always null
Any ideas what to try next?

I see the problem, quacks loudly like a bug. There's a workaround, you can subscribe the ContextMenuStrip's Opening event. At that point, well before you start navigating into the sub-items, the SourceControl property is still valid. So store it in a field of the class so you'll have it available in the Click event handler. Roughly:
private Control _tileCmsSource;
private void _tileContextMenuStrip_Opening(object sender, CancelEventArgs e) {
_tileCmsSource = _tileContextMenuStrip.SourceControl;
}
void tsmi_Click(object sender, EventArgs e)
{
ToolStripMenuItem tsmi = sender as ToolStripMenuItem;
// Use _tileCmsSource here
//...
}

Related

Winform Timer isn't running if statement when criteria is met

Hi I've come across either a weird bug or I'm not understanding something.
To cut a long story short I've had everything I'm wanting to work on my form working fine, I then decided to turn the form into an options menu so it was no longer the first form that appears when the application is launched and is shown after I click a button on a different form with the code
private void ShowOptionsButton_Click(object sender, EventArgs e)
{
formHomePage.Show();
}
And for some reason a timer if statement is no longer working:
private void StartSubCheckT_Tick(object sender, EventArgs e)
{
if (subliminalMessages.Count > 0)
{
MessageBox.Show("list greater than 0 if");
StartSubB.Enabled = true;
}
there are other if statements below but are irrelevant and the point of this is to make a button usable once a list is greater than 0. I've created another test button to display the value and it shows that the sublminalMessages list is greater than 0
private void testbutton3_Click(object sender, EventArgs e)
{
MessageBox.Show(subliminalMessages.Count.ToString());
}
Which outputs at 1 which it should be from some other code that adds a value in at the beginning. But for some reason even with the subliminalmessages.count being greater than 0 the if statement is no longer being called ever since I'm making the form appear being called from another form from the button code above.
The subliminalMessages list is being populated and created on the same form
public List<string> subliminalMessages = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(Settings.Default["Subliminal1"].ToString()))
{
subliminalMessages.Add(Settings.Default["Subliminal1"].ToString());
MessageBox.Show("If worked");
}
}
There is a value in the Setting.Default that is being added
The button and timer are on the same form and the timer in question is enabled.
Does anyone know why?
Thanks
I'll have a stab at giving you an answer. But it's a little swerve from what you're doing now.
From what I understand of your code you are using the timer to enable/disable the StartSubB button. Or maybe just enable it.
Instead of relying on the timer which appears to not work why not use a BindingList<string>. This has an event called ListChanged which you can handle and then enable/disable your button.
Here's a test form I created:
public partial class Form1 : Form
{
BindingList<string> items;
public Form1()
{
InitializeComponent();
button3.Enabled = false;
items = new BindingList<string>();
items.ListChanged += Items_ListChanged;
}
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
button3.Enabled = items.Count > 0;
}
private void btnAdd_Click(object sender, EventArgs e)
{
items.Add("a");
}
private void btnRemove_Click(object sender, EventArgs e)
{
if (items.Count > 0)
items.RemoveAt(items.Count - 1);
}
}
I have a BindingList<string> called items. This is analagous with your subliminalmessages list but it's a BindingList. This type of list has a ListChanged event that fires when items are added or removed from the list. In the constructor we new up the items list and subscribe to the ListChanged event.
When the Items_ListChanged event fires button3 is enabled or disabled based on whether items.Count > 0 or not.
In btnAdd_Click we just add an item to the list.
In btnRemove_Click we check that there are some items then remove the last one.
If you were to run this you'd see that when we click the Add button, button3 gets enabled. If we click the Remove button we'll see button3 get disabled.
The only limitation of BindingList is that you can't add a range by passing in another List.
If you implement this and your button still doesn't activate then you'll probably need to post some more code. Strip out all the irrelevant stuff and put it in a new project that demonstrates the failure of the condition and either copy the code here or provide a link to download the project. The only reason the if statement should fail is if the list is actually empty.

How can I prevent a WP7 context menu from closing after an item has been selected?

I have hooked up a context menu item in a listbox item such that tapping it will change its state. I need either the menu to stay open after the item has been selected, or to programmatically reopen the menu right after it closes.
My menu looks like so:
Some Command 1
Some Command 2
Some Command 3
Inverted
And the user can tap the Inverted command and then tap one of the other commands to cause them to function in Inverted Mode, and the menu through data binding appears like so:
Some Command 1
Some Command 2
Some Command 3
Inverted ✔
Not being able to figure out how to keep the menu open after tap, I've tried the less ideal reopen menu approach like so:
private void onCommandInvert(object sender, RoutedEventArgs e)
{
CommandState.Instance.Inverted = !CommandState.Instance.Inverted;
// Open it again.
MenuItem menuItem = (MenuItem)sender;
ContextMenu menu = (ContextMenu)menuItem.Parent;
menu.IsOpen = true;
}
But doing so throws the following exception on the menu.IsOpen = true statement:
A first chance exception of type 'System.InvalidOperationException' occurred in
System.Windows.dll
An unhandled exception of type 'System.InvalidOperationException' occurred in
System.Windows.dll
Additional information: Element is already the child of another element.
I have also tried the following with the Closed event, with the same exception occurring:
private void onContextMenuClosed(object sender, RoutedEventArgs e)
{
ContextMenu menu = (ContextMenu)sender;
menu.IsOpen = true;
}
Any ideas? Thanks!
I got it! Thanks to willmel's comment, I digged through the source code for the MenuItem and was able to override OnClick() to do exactly what I needed (the ideal solution no doubt). I couldn't access Click however, so I needed to introduce a StayClick event property as well.
Enjoy!
using Microsoft.Phone.Controls;
using System.Windows;
namespace MyNamespace
{
public class MenuItemEx : MenuItem
{
public bool StayOpenWhenClicked
{
get;
set;
}
public event RoutedEventHandler StayClick;
protected override void OnClick()
{
if (StayOpenWhenClicked)
{
if (StayClick != null)
{
StayClick.Invoke(this, new RoutedEventArgs());
}
}
else
{
base.OnClick();
}
}
}
}
and in the page's xaml, instead of toolkit:MenuItem you use my:MenuItemEx
<my:MenuItemEx
Header="Inverted"
StayClick="onCommandInvert"
StayOpenWhenClicked="True"
/>
If you want to persist the menu after the user has selected an item then I believe the Context menu control is not what you should be using.
Better you should create your own user control to mimic the behaviour and have it place appropriately on the screen where it makes sense (to the side or above/below)
Alternatively if these are options to be enacted on selected items consider using the application bar icons / menu items and write the event code to read the currently selected value of the listbox item.
Hope this helps.

cannot retrieve object of TreeViewItem

I have a tree view in silver light which i am creating dynamically from my code behind on the load event of my .xaml page. My treeview contains numerous treeviewitems.The header of my treeviewitem contains a stack panel. My stackpanel contains a check box as child. I have created an event handler for Unchecked event of the check box.
Now here is my problem.
When the unchecked event of my check box is triggered i want to retrieve the object of treeviewitem that is consuming the check box.
Here is the code snippet that shows how i am creating my treeviewitem
objTreeviewItem = new TreeViewItem();
objStackPanel = new StackPanel();
objStackPanel.Orientation = Orientation.Horizontal;
objCheckBox = new CheckBox();
objCheckBox.Content = "Checkbox1";
objCheckBox.Unchecked += new RoutedEventHandler(objCheckBox_Unchecked);
objStackPanel.Children.Add(objCheckBox);
objTreeviewItem.Header = objStackPanel;
Here is the code snippet for unchecked event of the checkbox
void objCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
try
{
TreeViewItem objItem = (((e.OriginalSource) as CheckBox).Parent as StackPanel).Parent as TreeViewItem;
}
catch (Exception ex)
{
}
}
The above statement in the try block is returning me a null value. Hence i am not able to retrieve the treeviewitem which is consuming the check box on which event has been triggered.
So is there any other Property or method(other then the parent property) which can help me get the treeviewitem.
Please any kind of help will be appreciated
The OriginalSource property does not return your CheckBox but whatever control on which the event started which may be a TextBlock inside the CheckBox, cast the sender instead, it will always be the control the event is associated with.

WPF DataGrid Button Click in New Line Gets the wrong line in Current Item

I have a WPF DataGrid with a button on one of the columns.
When I click the button I have this function called:
MyClass mySC = (MyClass)(CollectionViewSource.GetDefaultView(grdMyClass.DataContext).CurrentItem);
This code works perfect, but when I click on the button on the new line (the last on on the grid) I get the msSC of the line before it, and not null, or something that related to the last new line.
how can I check if the button was clicked in the new line ?
If your intention is that clicking on a button should do something with the data bound to that row then just get the data from the button's data context. If null then user clicked on an empty row.
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass data = (sender as FrameworkElement).DataContext as MyClass;
}
I just got the exact same problem and created my own solution.
I have a DataGrid with ItemsSource binded to a list in my ViewModel, and SelectedIndex binded to an int in the viewmodel, in order to be able to play with the list when we choose something (and for example simulate an event such as "OnSelectionChanged")
So the solution is very simple here:
//App selected on the list
private int _selectedApp;
public int SelectedApp
{
get { return _selectedApp; }
set
{
if (value == _listApps.Count) _selectedApp = -1;
else _selectedApp = value;
OnPropertyChanged("SelectedApp");
}
}
I just check that the index isn't out of range: if it is, I set it to -1, so my app considers nothing is actually selected.
Hope this can help, feel free to ask more :)

WinForms TabControl validation: Switch to a tab where validation failed

I currently have a Form with a TabControl containing some TabPages. Each TabPage has several controls with validation logic and appropriate ErrorProviders. On my OK_Button_Clicked event, I call Form.ValidateChildren() in order to determine whether to save and close the form . Now, suppose I have a control in tab 1 that fails validation, but the currently visible tab is tab 2. When the user presses OK, he would get no visual indication as to why the form is not closing. I would like to be able to automatically switch to a tab where validation failed, so the user would see the ErrorProvider's indication of error.
One approach would be subscribing to the Validated and validating events of all appropriate controls, and knowing which tab each of them is in, whenever one fails validation, a list of tabs that failed validation could be built. Since no ValidationFailed event is generated as far as I know, this could be cumbersome (e.g. defining a boolean for each control, setting it to false before validation and to true on its Validated event). And even if I had such an event, I would be forced to listen to many validation events, one for each control that might fail validation, and maintain the list of unvalidated tabs in code. I should note here that subscribing directly to the TabPage's validation events doesn't work, because they pass as validated even if controls contained inside them fail validation.
Another approach could leverage the fact that the controls in my TabPage happen to be custom controls. I could then make them implement an interface such as:
interface ILastValidationInfoProvider
{
public bool LastValidationSuccessful {get; set;}
}
For example:
public MyControl : UserControl, ILastValidationInfoProvider
{
MyControl_Validing(object sender, object sender, CancelEventArgs e)
{
if (this.PassesValidation())
this.ErrorProvider.SetError(sender, null);
LastValidationSuccessful = true;
else
e.Cancel = true;
this.ErrorProvider.SetError("Validation failed!", null);
LastValidationSuccessful = false;
}
}
And then, after the call to ValidateChildren I could use code such as:
public void OK_Button_Click
{
if (form.ValidateChildren())
this.Close()
else
foreach (TabPage tab in this.TabControl)
foreach (Control control in tab.Controls)
{
ValidationInfo = control as ILastValidationInfoProvider
if (ValidationInfo != null && !ValidationInfo.LastValidationSuccessful)
{
this.TabControl.SelectTab(tab);
return;
}
}
}
I like this approach better but it doesn't cater to cases where the controls being validated are not custom.
I would gladly use a better approach. Any ideas?
EDIT I am using Form.AutoValidate = EnableAllowFocusChange (as recommended by Chris Sells in his WinForms book), So the focus can indeed change from controls that failed validation (including moving to other tabs). I have also updated the sample code for the custom control to emphasize the fact that the ErrorProvider resides internally inside it.
OK so I finally figured it out.
I keep a dictionary whose keys are the TabPages and the values are HashSets of unvalidated controls within the corresponding tab. This is easily done by subscribing to all the validating and validated events of the controls in each tab. Finally, in OK_BUtton_Click, if ValidateChildren fails, I know one of the hashsets will be none empty and I simply jump to the first unvalidated tab (only if the currently selected tab doesn't have any error itself).
Dictionary<TabPage, HashSet<Control>> _tabControls
= new Dictionary<TabPage, HashSet<Control>>();
public OptionsForm()
{
InitializeComponent();
RegisterToValidationEvents();
}
private void RegisterToValidationEvents()
{
foreach (TabPage tab in this.OptionTabs.TabPages)
{
var tabControlList = new HashSet<Control>();
_tabControls[tab] = tabControlList;
foreach (Control control in tab.Controls)
{
var capturedControl = control; //this is necessary
control.Validating += (sender, e) =>
tabControlList.Add(capturedControl);
control.Validated += (sender, e) =>
tabControlList.Remove(capturedControl);
}
}
}
private void Ok_Button_Click(object sender, EventArgs e)
{
if (this.ValidateChildren())
{
_settings.Save();
this.Close();
}
else
{
var unvalidatedTabs = _tabControls.Where(kvp => kvp.Value.Count != 0)
.Select(kvp => kvp.Key);
TabPage firstUnvalidated = unvalidatedTabs.FirstOrDefault();
if (firstUnvalidated != null &&
!unvalidatedTabs.Contains(OptionTabs.SelectedTab))
OptionTabs.SelectedTab = firstUnvalidated;
}
}
I think it's pretty sweet !

Resources