I'm developing a TreeView based control and my double click event continues to bubble up my TreeViewItem nodes.
The goal is to have the TreeViewItem expand or collapse when it is double clicked.
I have a style that applies an event handler for the MouseDoubleClick event to each TreeViewItem.
Here's the code that handles the event
private void TreeViewItemDoubleClicked( object sender, RoutedEventArgs e )
{
// Get the specific tree view item that was double clicked
TreeViewItem treeViewItem = sender as TreeViewItem;
// not null?
if( null != treeViewItem )
{
// Switch expanded state
if( true == treeViewItem.IsExpanded )
{
treeViewItem.IsExpanded = false;
}
else
{
treeViewItem.IsExpanded = true;
}
// Set event handled
e.Handled = true; // [1]
}
}
This works fine for the top level TreeViewItem however when a child is double clicked, the event bubbles up the tree causing the entire branch to collapse. Why is the event continuing to bubble? As noted a [1] I'm setting the event as handled.
Hate answering my own questions but here is the solution that I ultimately came to use.
After coming across a few sources that specified that the MouseDoubleClick is raised for each TreeViewItem in the branch ( from child up to the root ) regardless if the event is handled I utilized the answer from this question:
WPF TreeView, get TreeViewItem in PreviewMouseDown event
to get the TreeViewItem that was under the mouse event. If the current sender is equal to the TreeViewItem of the mouse event I expand/collapse as required. Otherwise, I just ignore the event and do nothing.
No idea why, but the selected answer didn't work for every TreeViewItems for me. So I used a simple bool approach to fence from reeintering TreeViewItemDoubleClicked more than once.
private void TreeViewItemDoubleClicked( object sender, RoutedEventArgs e )
{
// Make sure the event has never been handled first
if (bubblingBulkwark)
return;
// Get the specific tree view item that was double clicked
TreeViewItem treeViewItem = sender as TreeViewItem;
// not null?
if( null != treeViewItem )
{
// Switch expanded state
if( true == treeViewItem.IsExpanded )
{
treeViewItem.IsExpanded = false;
}
else
{
treeViewItem.IsExpanded = true;
}
// Raise bulkwark
bubblingBulkwark = true;
}
}
To allow the very first handler invoked to execute fully (therefore relying on the fact that a child's handler will be called before it's parent's), simply add :
private void TreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
bubblingBulkwark = false;
}
And don't forget to register it.
treeView.PreviewMouseDown += TreeView_PreviewMouseDown;
Related
I've got a MouseDown event on my GridView:
private void gdcSVNDefaultView_MouseDown(object sender, MouseEventArgs e)
{
var vw = (GridView)sender;
var hitInfo = vw.CalcHitInfo(e.Location);
DXMouseEventArgs.GetMouseArgs(e).Handled = SelectChildRows(hitInfo.RowHandle, vw);
}
private static bool SelectChildRows(int r, GridView view)
{
if (!view.IsGroupRow(r) || !view.GetRowExpanded(r))
return false;
var childRowCount = view.GetChildRowCount(r);
var first = view.GetChildRowHandle(r, 0);
var last = (first + childRowCount - 1);
view.SelectRange(first, last);
return true;
}
Screenshot of my form since not everyone is familiar with the DevExpress grids and might not know what i mean by a 'Group':
When you have a Group in the grid and click on that Group row instead of an actual 'data' row, I want to select all the child rows belonging to that Group.
The code works. If I click on a Group (for example, Type: Warning in my screenshot) and hold the mouse down I can see it select all the child rows... But as soon as I let up on the mouse, it de-selects them and selects just the group row. So if you just click quickly, like you normally would, you see them all flash quickly as as their selected state toggles.
Unfortunately, the Mouse events don't have any sort of "Handled" property I can set to make the MouseUp / Click not fire. I tried moving the code in my MouseDown to the MouseUp event and it doesn't even temporarily select everything. DX also has a "RowClick" event, tried it there... same results as the MouseUp.
Any ideas on how I can "cancel" those events?
Edit: Turns out there is a Handled property if you cast the MouseEventArgs to a DXMouseEventArgs object... But it still observes the same behavior.
Alright, so I found out that I need to set Handled in both the MouseDown and MouseUp events in order to get the behavior desired:
public class MyForm
{
private bool _b;
private void gdcSVNDefaultView_MouseDown(object sender, MouseEventArgs e)
{
var vw = (GridView)sender;
var hitInfo = vw.CalcHitInfo(e.Location);
if (hitInfo.HitTest == GridHitTest.RowGroupButton)
return;
_b = false;
if (vw.IsGroupRow(hitInfo.RowHandle))
_b = SelectChildRows(hitInfo.RowHandle, vw);
DXMouseEventArgs.GetMouseArgs(e).Handled = _b;
}
private void gdcSVNDefaultView_MouseUp(object sender, MouseEventArgs e)
{
DXMouseEventArgs.GetMouseArgs(e).Handled = _b;
}
private static bool SelectChildRows(int r, GridView view)
{
if (!view.GetRowExpanded((r)))
return false;
if (ModifierKeys != Keys.Shift && ModifierKeys != Keys.Control)
view.ClearSelection();
var childRowCount = view.GetChildRowCount(r);
var first = view.GetChildRowHandle(r, 0);
var last = (first + childRowCount - 1);
view.SelectRange(first, last);
return true;
}
}
Note: This IF block is NOT required for the core functionality. I found that if I click on multiple Groups it was adding that Range to the current Selection, which might be unexpected behavior for the end user. I added a check to see if the user is holding down the Control or Shift keys when they click the Group row. If not, then clear the current selection and select the new range
Have you tried using the Click event instead of MouseDown? Might be worth a shot. From here:
Note that the Click event also fires after the mouse button has been released. The difference is that the Click event is only raised if the mouse pointer is within the View when releasing the mouse button.
I have a bunch of paths programatically nested inside of a canvas. I'm basically trying to figure out how click bubbling works. How do I setup the canvas event handler to check if the point of the click was also on a path nested inside of the canvas. This is my basic even code that works if the paths are not nested.
How do I add bubbling click detection?
void Path_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var item = ((FrameworkElement)e.OriginalSource).DataContext as Path;
if (item != null)
{
MessageBox.Show(item.Name);
}
}
you add a handler on the event UIElement.MouseLeftButtonUpEvent (as Path inherits it from there) on the Canvas
theNestingCanvas.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(target));
private void handler(object asd, RoutedEventArgs e)
{
Path p = e.OriginalSource as Path;
if (p != null)
{
//do whatever
}
e.Handled = true;
}
like that you catch all bubbled UIElement.MouseLeftButtonUp events of elements within the canvas which are not handled somewhere else yet...
of course you can also add the handler on the Event Path.MouseLeftButtonUpEvent but after you will ask yourself why you catch also the MouseUp events of other nested elements...
I'm new to WPF. In my WPF app, I have Windows which contain a user defined child control and that user defined child control again contains another user defined child control. Now from the inner most child control, on a button click, I want to fire events on all three controls (i.e. First Grand Child Control, Second Child Control, Third Main Control, and Window).
I know this can be achieved through delegates and Event Bubbling. Can you please tell me how?
Most important piece pf code for that:
Add the event handlers on the static UIElement.MouseLeftButtonUpEvent:
middleInnerControl.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(handleInner)); //adds the handler for a click event on the most out
mostOuterControl.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(handleMostOuter)); //adds the handler for a click event on the most out
The EventHandlers:
private void handleInner(object asd, RoutedEventArgs e)
{
InnerControl c = e.OriginalSource as InnerControl;
if (c != null)
{
//do whatever
}
e.Handled = false; // do not set handle to true --> bubbles further
}
private void handleMostOuter(object asd, RoutedEventArgs e)
{
InnerControl c = e.OriginalSource as InnerControl;
if (c != null)
{
//do whatever
}
e.Handled = true; // set handled = true, it wont bubble further
}
Have a look at this http://msdn.microsoft.com/en-us/library/ms742806.aspx
This page explains all about routed events, including how to implement and consume them.
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)
I have a C# ComboBox using WPF. I have code that executes when the ComboBox's GotFocus is activated. The issue is that the GotFocus event is executed every time a selection is made from the ComboBox. For example, the GotFocus is executed when you first click on the ComboBox and then when you make a selection even though you have not click on any other control.
Is it possible to prevent this event from firing if a selection is being made in the list or is there a flag or something else in the event handler that can be used to determine if the GotFocus event handler was fired as a result of the user selecting an item in the list?
You can solve this problem with next verification:
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(ComboBoxItem))
return;
//Your code here
}
This code will filter all focus events from items (because they use bubble routing event). But there is another problem - specific behaviour of WPF ComboBox focus: when you open drop-down list with items your ComboBox losing focus and items get. When you select some item - item losing focus and ComboBox get back. Drop-down list is like another control. You can see this by simple code:
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
Trace.WriteLine("Got " + DateTime.Now);
}
}
private void myComboBox_LostFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
Trace.WriteLine("Lost " + DateTime.Now);
}
}
So you will get anyway atleast two focus events: when you select ComboBox and when you selecting something in it (focus will return to ComboBox).
To filter returned focus after selecting item, you can try to use DropDownOpened/DropDownClosed events with some field-flag.
So the final code with only 1 event of getting focus:
private bool returnedFocus = false;
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem) && !returnedFocus)
{
//Your code.
}
}
private void myComboBox_LostFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
ComboBox cb = (ComboBox)sender;
returnedFocus = cb.IsDropDownOpen;
}
}
Choose from this examples what you actually need more for your application.
I'm not too hot on WPF; but if you're trying to detect changes to the list (click on new value etc) you can use SelectedIndexChanged events..
On the other hand, if you really do want to know simply when the control is focussed, can you filter it by saying something like;
if (combo1.Focused && combo1.SelectedIndex == -1)
{
...
}
.. ? It really depends on what youre trying to detect, exactly.
Another solution is used is to determine whether the new focused element is an existing item in the combobox. If true then the LostFocus event should not be performed, because the combobox still has focus. Otherwise an element outside the combobox received focus.
In the code snipplet below I added the functionality in a custom combobox class
public class MyComboBox : System.Windows.Controls.Combobox
{
protected override void OnLostFocus(RoutedEventArgs e)
{
//Get the new focused element and in case this is not an existing item of the current combobox then perform a lost focus command.
//Otherwise the drop down items have been opened and is still focused on the current combobox
var focusedElement = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this));
if (!(focusedElement is ComboBoxItem && ItemsControl.ItemsControlFromItemContainer(focusedElement as ComboBoxItem) == this))
{
base.OnLostFocus(e);
/* Your code here... */
}
}
}