TreeView fires the BeforeSelect event multiple times - c#

I am using the Windows Forms TreeView control.
The way i have it hooked up is as followed (simplified):
TreeView treeView = new TreeView();
treeView.BeforeSelect += beforeSelect;
private void beforeSelect(sender, args)
{
MessageBox.Show("Some msg");
// more code
}
In certain scenarios, the call to MessageBox.Show triggers another raising of the BeforeSelect event, which triggers another, and another, ...
It seems this event is raised PER ITEM in the treeview (i have counted the number of times it is raised).
I have searched all over the internet on more information for why this could occur.
One thing i've found was that TreeView will automatically select the first node when gaining focus.
This does not explain however why the event is fired as the number of treenode items in the tree.
Any help would be appreciated on this. I am considering raising a Microsoft Connect bug for this, as it seems like a very weird behavior that is not consistent with how i think the control should work.

Would it be enough to simply block yourself like the following?
private bool _inside;
private void beforeSelect( object sender, EventArgs args )
{
if ( !_inside )
{
_inside = true;
MessageBox.Show("Some msg");
// more code
_inside = false;
}
}
This would disallow "recursive" calls of your function.

the BeforeSelect event isn't fired multiple times by default.
when you select a node, you show a dialog(here messagebox) which interrupts the selection event or task however and after you close the dialog the selection event fires again based on the interruption. You should use AfterSelect event of the treeview to do things... and BeforeSelect only for validation..
Please look at this code - run it
void treeView1_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
e.Node.Tag = (int)(e.Node.Tag ?? 0) + 1;
int count = (int)(e.Node.Tag);
e.Node.Text = String.Format("selected {0} Count: {1}", e.Action.ToString(), count);
}

When you define an object,you should write like this;
True Write:
private static TreeView projectagac;
...
...
...
projectagac = new TreeView();
thus you will create only one object.

Related

Hooking up generic event handlers to multiple controls of the same type

I have a WinForms app that contains many NumericUpDown controls. In a nutshell, if my users enter a value into the control and then delete the text, I want to restore it (the text) when the control loses focus. So I decided that I'd check .Text when the control loses focus and if it's empty, I set .Text = .Value.ToString().
I'm doing this in the Leave event handler and it works just fine. But as I said, I have many of these controls (18, to be exact). I don't like creating 18 Leave event handlers that all do the same thing so I created a generic one like this:
private void numericUpDown_GenericLeave(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(((NumericUpDown)sender).Text))
((NumericUpDown)sender).Text = ((NumericUpDown)sender).Value.ToString();
}
I started to hook up all of the controls to this generic event handler but I quickly got tired of doing this:
numericUpDown1.Leave += numericUpDown_GenericLeave;
numericUpDown2.Leave += numericUpDown_GenericLeave;
numericUpDown3.Leave += numericUpDown_GenericLeave;
...
numericUpDown18.Leave += numericUpDown_GenericLeave;
So I thought I'd create a function that would return a list of all the controls of a specified type and then loop through that list and hookup the event handlers. That function looks like this:
public static List<Control> GetControlsOfSpecificType(Control container, Type type)
{
var controls = new List<Control>();
foreach (Control ctrl in container.Controls)
{
if (ctrl.GetType() == type)
controls.Add(ctrl);
controls.AddRange(GetControlsOfSpecificType(ctrl, type));
}
return controls;
}
I call the function like this:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
foreach (var numericUpDownControl in listOfControls)
{
numericUpDownControl.Leave += numericUpDown_GenericLeave;
}
When I run my app, however, I don't see the expected behavior that occurs when I manually hookup each control to the generic event handler. This code is currently in the constructor of my form and I've tried calling it before as well as after the call to InitializeComponent() but neither one seems to be working. I get no error of any kind, I just don't see the behavior that I was expecting. I have a breakpoint set inside the generic event handler but the debugger never breaks so it seems like the event handler isn't being hooked up correctly. Does anyone know why this might be or how I can troubleshoot it further? Thanks!
EDIT
I just realized that the call to:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
was happening before the call to InitializeComponent() so of course the list of controls being returned was empty. DOH! Thanks for all the replys. I apologize for wasting everyones time. :-(
You're passing this to your method, which is presumably a reference to your form. Your method will only catch the controls that are placed directly on your form. Any NumericUpDown controls that are not directly on the form (i.e. they're sitting on a panel or something) will be missed.
Why not create a user control that has a NumericUpDown control in it.
Then handle this is in the user control events.
This worked for me:
private decimal _previous = 0;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (((NumericUpDown)sender).Text.Length > 0)
{
_previous = this.numericUpDown1.Value;
}
}
private void UserControl1_Leave(object sender, EventArgs e)
{
if (this.numericUpDown1.Text == "")
{
this.numericUpDown1.Value = _previous;
this.numericUpDown1.Text = System.Convert.ToString(_previous);
}
}
Just note that the Leave event is on the user control not on the updown control itself.
Question answered. See Edit above. Thanks to bsegraves for pointing me in the right direction.

ListView ItemChanged puzzle with MessageBoxes - ItemSelectionChanged called twice

Here is the problem: I have a Windows Forms application that I'm developing, and in one segment I'm using a ListView control.
What I'm trying can be simply stated as: on event ListViewItemSelectionChange show a MessageBox for user to confirm the change, if not confirmed change to let's say the first item. This change to the first item would again fire ListViewItemSelecionChange, so I unregister and re-register the event handler method, so everything should be good, right?
What actually happens is that the handler method is called twice (actually ListView should fire two events on Selection change, one for deselect, other for newly selected item, but I have an e.IsSelected statement at the beginning to catch only selected items, so actually you could say that there are four events fired).
The problem is, if I generated the first event with mouse click on ListView item, and I've unsubscribed before programatically changing to the first item, what generates the second event firing? Is it some focus change because of the MessageBox call? Is there any way to prevent the second event to fire?
I have a simple example solution here, it can't be more simlified (25 SLOC), so if you can, please take a look. Note that commenting the line "if (ShowMessageBox())" stops the second event from firing, is this some focus change problem?
http://www.filedropper.com/listviewtestwithmsgbox
Edit: the relevant code:
private void listViewWithSelection1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
// listview actually generates two ItemSelectionChanged events,
// one for deselect of a item, and another event for a newly selected item (which we want here).
if (e.IsSelected)
{
if (ShowMessageBox())
Button1_Click(null, EventArgs.Empty);
label1.Text += "item selected ";
}
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void Button1_Click(object sender, EventArgs e)
{
// change ti first ListView item
listView1.ItemSelectionChanged -= listViewWithSelection1_ItemSelectionChanged;
listView1.Items[0].Selected = true;
listView1.ItemSelectionChanged += listViewWithSelection1_ItemSelectionChanged;
}
Hmm, can you describe how the selection is being changed to begin with? If it's by the user clicking to select an item, perhaps catch the Click or DoubleClick event rather than the ItemSelectionChanged event? I have this snippet I'm using on a program currently. If the user double clicks the list box (listView, in your case), do something with the selected item.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void listView1_Click(object sender, EventArgs e)
{
if (ShowMessageBox())
listView1.TopItem.Selected = true;
label1.Text += "item selected ";
}
}
Edited to include relevant code.
One way to do this is to have a flag which says should the on change code run.
In your ListViewItemSelecionChange code you check the value of the flag and run code accordingly.

How do you respond ONCE to a user selecting a range in a ListView control?

I'm trying to handle the user changing which items are selected in a listbox (by updating information about what's selected), but if you select a range (using shift+select), it actually fires a separate 'ItemSelectionChanged' event once for EACH item that was selected/deselected, i.e. if you selected 100 items, you get 100 events, and the first time the event handler's called, it seems to have no way of knowing that there's more to come.
Is there a way to not respond until the process of selecting/deselecting items is complete?
There is no SelectedItemsChanged event, I'm guessing you mean SelectedIndexChanged. What you can do is leverage the Control.BeginInvoke() method. The delegate target starts running when the UI thread goes idle again, after all the events have been fired. Make it look like this:
bool listUpdated = false;
private void listView1_SelectedIndexChanged(object sender, EventArgs e) {
if (!listUpdated) {
this.BeginInvoke(new MethodInvoker(updateList));
listUpdated = true;
}
}
private void updateList() {
listUpdated = false;
// etc...
}

Modifying ComboBox SelectedIndex Without Triggering Event in C#

My C# application has a comboBox with a SelectedIndexChanged event. Usually, I want this event to fire, but but sometimes I need the event to not fire. My comboBox is an MRU file list. If a file in the list is found to not exist, the item is removed from the comboBox, and the comboBox SelectedIndex is set to zero. However, setting the comboBox SelectedIndex to zero causes the SelectedIndexChanged event to fire, which in this case is problematic because it causes some UIF code to be run in the event handler. Is there a graceful way to disable/enable events for C# form controls? Thanks.
Start the eventhandler method with
ComboBox combo = sender as ComboBox;
if (combo.SelectedIndex == 0)
{
return;
}
If you're issue is with a different eventhandler you could remove the eventhandler's event registration first.
combo.SelectedIndexChanged -= EventHandler<SelectedIndexChangedEventArgs> SomeEventHandler;
combo.SelectedIndex = 0;
combo.SelectedIndexChanged += EventHandler<SelectedIndexChangedEventArgs> SomeEventHandler;
I have encountered this many times over the years. My solution is to have a class level variable called _noise and if I know I am about to change the index of combo or any other similiar control that fires when the selected index changes, I do the following in code.
private bool _noise;
Here is the code for the control event handler
private void cbTest_SelectedIndexChange(object sender, EventArgs e)
{
if (_noise) return;
// process the events code
...
}
Then when I know I am going to change the index, I do the following:
_noise = true; // cause the handler to ignore the noise...
cbTest.Index = value;
_noise = false; // let the event process again
I'm surprised there isn't a better way of doing this, but this is the way I do it. I actually use the Tag field of most controls so I don't have to subclass the control. And I use true/null as the values, since null is the default.
Of course, if you are actually using Tag, you'll need to do it differently...
In handler:
private void control_Event(object sender, EventArgs e)
{
if (control.Tag != null ) return;
// process the events code
...
}
In main code
try
{
control.Tag = true;
// set the control property
control.Value = xxx;
or
control.Index = xxx;
or
control.Checked = xxx;
...
}
finally
{
control.Tag = null;
}
One (fairly ugly) way would be to set a flag in the code that deletes the entry and then check that in the SelectedIndexChanged handler:
if (!deletedEntry)
{
// Do stuff
}
deletedEntry = false;
A better way might be to remove your SelectedIndexChanged event handler at the start of the delete method and reinstate it at the end. This way you code won't know the index has changed.
There's a better way!
combo_box = QComboBox() # your combobox
combo_box.blockSignals(True)
combo_box.setCurrentIndex(self, ix)
combo_box.blockSignals(False)

Is there a "All Children loaded" event in WPF

I am listening for the loaded event of a Page. That event fires first and then all the children fire their load event. I need an event that fires when ALL the children have loaded. Does that exist?
I hear you. I also am missing an out of the box solution in WPF for this.
Sometimes you want some code to be executed after all the child controls are loaded.
Put this in the constructor of the parent control
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => {code that should be executed after all children are loaded} ));
Helped me a few times till now.
Loaded is the event that fires after all children have been Initialized. There is no AfterLoad event as far as I know. If you can, move the children's logic to the Initialized event, and then Loaded will occur after they have all been initialized.
See MSDN - Object Lifetime Events.
You can also use the event: ContentRendered.
http://msdn.microsoft.com/en-us/library/ms748948.aspx#Window_Lifetime_Events
WPF cant provide that kind of an event since most of the time Data is determining whther to load a particular child to the VisualTree or not (for example UI elements inside a DataTemplate)
So if you can explain your scenario little more clearly we can find a solution specific to that.
One of the options (when content rendered):
this.LayoutUpdated += OnLayoutUpdated;
private void OnLayoutUpdated(object sender, EventArgs e)
{
if (!isInitialized && this.ActualWidth != 0 && this.ActualHeight != 0)
{
isInitialized = true;
// Logic here
}
};
Put inside your xaml component you want to wait for, a load event Loaded="MyControl_Loaded" like
<Grid Name="Main" Loaded="Grid_Loaded"...>
<TabControl Loaded="TabControl_Loaded"...>
<MyControl Loaded="MyControl_Loaded"...>
...
and in your code
bool isLoaded;
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
isLoaded = true;
}
Then, inside the Event triggers that have to do something but were triggering before having all components properly loaded, put if(!isLoaded) return; like
private void OnButtonChanged(object sender, RoutedEventArgs e)
{
if(!isLoaded) return;
... // code that must execute on trigger BUT after load
}
I ended up doing something along these lines.. your milage may vary.
void WaitForTheKids(Action OnLoaded)
{
// After your children have been added just wait for the Loaded
// event to fire for all of them, then call the OnLoaded delegate
foreach (ContentControl child in Canvas.Children)
{
child.Tag = OnLoaded; // Called after children have loaded
child.Loaded += new RoutedEventHandler(child_Loaded);
}
}
internal void child_Loaded(object sender, RoutedEventArgs e)
{
var cc = sender as ContentControl;
cc.Loaded -= new RoutedEventHandler(child_Loaded);
foreach (ContentControl ctl in Canvas.Children)
{
if (!ctl.IsLoaded)
{
return;
}
}
((Action)cc.Tag)();
}

Categories