Event Bubbling in WPF Application - c#

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.

Related

ContextMenu on disabled controls such as buttons

I'm looking for a way to enable/disable buttons using theirContextMenu. But my problem is when I click on the Enable MenuItem to disable the button the ContextMenu won't show anymore.
Is there a way to keep the ContextMenu or other behavior on a disabled Control?
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("Enable", new EventHandler(enableButton));
this.button1.ContextMenu = cm;
private void enableButton(object sender, EventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem != null)
{
ContextMenu menu = menuItem.GetContextMenu();
Control sourceControl = menu.SourceControl;
sourceControl.Enabled = !sourceControl.Enabled;
}
}
Make your own disabling functionality, set the buttons to grayed out, intercept their events, this way the button will appear disabled but be enabled to receive the ContextMenu event. This behaviour is "By-Design".
You can easily work out the btn.Font to look disabled. For the events one way is to unsubscribe them on Disable, and Hook them up on Enable, here is an example on getting a controls events...
dynamic controltype = btn;
var events = Type.ReflectionOnlyGetType(controltype.AssemblyQualifiedName, false, true).GetEvents();
foreach (var item in events)
{
//EventHandler<T> use a generic Event Handler to Subscribe and Unsubscribe
}
From MSDN
With the Enabled property, you can enable or disable controls at run
time. For example, you can disable controls that do not apply to the
current state of the application. You can also disable a control to
restrict its use. For example, a button can be disabled to prevent the
user from clicking it. If a control is disabled, it cannot be
selected.
and
When a container control has its enabled property set to false, all
its contained controls are disabled, as well. For example, if the user
clicks on any of the controls contained in a disabled GroupBox
control, no events are raised.
So, disabled control cannot raise any events.
You can make your own disable method which make button gayed and disable click events, and reverse enable method.
Based on #esiprogrammer sugestion you can do something like this:
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Point pt = e.Location;
Control ctrl = this.GetChildAtPoint(pt);
if (ctrl != null)
{
ContextMenu menu = ctrl.ContextMenu;
menu.Show(ctrl, new Point(10,10));
}
}
}
there is a workaround to detect right click on mouse up event of form and show context menu. and using this.GetChildAtPoint(e.Location) you can find which control you right clicked on.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right
&& this.GetChildAtPoint(e.Location)?.Name == "enableButton")
{
ContextMenu.Show();
}
}

Click event not registering when clicking on certain parts of UserControl

Refer to this previous question I asked where I couldn't get the click event for a UserControl on my form to fire off.
The way I have my control set up is I have the control itself sized to 50, 20. I then a have panel sized 25, 20 set to dock on the right side. In code within the UserControl itself, anytime the background of the control or the panel that acts as the "switch" are clicked, it fires off this code:
private void toggleClick(object sender, EventArgs e) {
if (toggleStatus) { // set to "on"
this.BackColor = Color.Red;
this.pnlSwitch.Dock = DockStyle.Left;
toggleStatus = false;
} else { // set to "off"
this.BackColor = Color.Green;
this.pnlSwitch.Dock = DockStyle.Right;
toggleStatus = true;
}
}
This works great and goes off every time. However, I put my UserControl in a form and tried to tie a click event to this method:
private void toggleSoundClick(object sender, EventArgs e) {
MessageBox.Show("Test");
}
When I click on the background of the control, this fires off and everything works like it should. However, if I click on the panel that acts as the "switch", the click event in my form doesn't fire (but the click event in the UserControl itself does, which isn't the issue). I can kind of understand this being something like z-indexing in CSS, but it still baffles me why a click event inside the control wouldn't cause it to fire off.
My question is, how can I get around this type of behavior?
edit I also can't integrate the behavior from the click event inside the form into the click event inside the UserControl. I have several of these controls on my form, and all have different behavior depending on which is clicked.
After taking Sinatr's suggestion I googled a bit more and found this post on here. What I ended up doing was unsubscribing all click events for the "switch" panel, then putting this code inside my UserControl.
public new event EventHandler Click {
add {
base.Click += value;
foreach (Control control in Controls) {
control.Click += value;
}
}
remove {
base.Click -= value;
foreach (Control control in Controls) {
control.Click -= value;
}
}
}
This made it so that when I subscribed the click event for the control itself to the toggleClick method, it also registered it with the "switch" panel (which is why I unregistered all other click events, otherwise it would fire off twice), and it also caused the toggleSoundClick method to be subscribed to the "switch" panel as well when I subscribed it to the control itself inside my form.
edit For my purposes I wanted to add the click events recursively to all controls, no matter the depth, so I changed the code to this
public new event EventHandler Click {
add {
subscribeToEvent(this, value);
}
remove {
unsubscribeFromEvent(this, value);
}
}
private void subscribeToEvent(Control control, EventHandler eventHandler) {
control.Click += eventHandler;
foreach (Control child in control.Controls) {
subscribeToEvent(child, eventHandler);
}
}
private void unsubscribeFromEvent(Control control, EventHandler eventHandler) {
control.Click -= eventHandler;
foreach (Control child in control.Controls) {
unsubscribeFromEvent(child, eventHandler);
}
}
In the UserControl code that is firing correctly, add
e.Handled = false;
By setting the EventArgs' Handled property to false, the click event is passed through and hopefully caught (again) by the listener in your form method.

C# WinForms - Looping through all controls to apply event handler, not working in SplitContainer?

I've got these functions:
private void setupFocusControls(Control parent)
{
foreach (Control control in parent.Controls)
{
control.GotFocus += HandleFocus;
}
}
private void HandleFocus(object sender, EventArgs e)
{
Control control = (Control)sender;
thisFormName = this.Name;
thisControlName = control.Name.ToString();
if (bHelpSystemActive)
{
bHelpSystemActive = false;
if ((ModifierKeys & Keys.Control) == Keys.Control)
{
HelpSystem hs = new HelpSystem(thisFormName, thisControlName);
hs.ShowDialog();
}
else
{
showTooltipForControl(control, thisFormName);
}
return;
}
}
And I call this in the Form_Load function:
private void Labeller_Load(object sender, EventArgs e)
{
setupFocusControls(this);
fillListBox();
}
What this does is show a custom help system I've written. If no control key is clicked, then I'll display the info in a tool tip. If the control key is pressed, then I show an editor. Simple really.
Now, this code works perfectly on another form, which uses panels as containers for my form controls. The problem is, I now want to add this functionality to a separate form. I've added all the code, but none of the controls on the form are having the HandleFocus event added to them. The only difference between this form and the working one is that it uses a splitContainer as it's container.
My question is, why is the setupFocusControls function not looping through the splitContainer as it does the panels on my working form? And, how would I go about fixing it? I'd obviously rather not have several functions to perform this (what I thought) simple task...
Cheers.
Assuming that the problem is that you are not assigning the event to every single control on the form (only top-level controls), the fix should be to change your setupFocusControls(Control) method:
private void setupFocusControls(Control parent)
{
foreach (Control control in parent.Controls)
{
control.GotFocus += HandleFocus;
// add the following line to recurse throughout the control tree
setupFocusControls(control);
}
}
This will add the HandleFocus event handler to every single control, by recursing through the children of every control. I hope this works for you!
As a bonus, if you want to add the event handler to all controls, including the parent control, you could write the setupFocusControls method as follows:
private void setupFocusControls(Control parent)
{
parent.GotFocus += HandleFocus;
foreach (Control child in parent.Children)
setupFocusControls(child);
}

Handled RoutedEvent continues to bubble up tree

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;

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