Why does the Handled property in PreviewMouseLeftButtonDownEvent affect a ClickEvent? - wpf

Consider you add a ClickEvent- and PreviewMouseLeftButtonDown-Handler for a Button
<Button x:Name="button"
Click="Button_Click"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown">
</Button>
When clicking the Button, first PreviewMouseLeftButtonDown is fired, then the Click-Event.
If you set e.Handled = true in the Preview...-Event, the Click-Event is not handled any more.
However, now let's think of the MouseLeftButtonDownEvent.
First, this event's routing strategy is direct. That is, it is re-raised for every control. In contrast, the Preview...-Event is tunneling, the Click-Event is bubbling.
Second, adding a MouseLeftButtonDownEventHandler is only successful when registering the handler such that it is even invoked for already handled events, as shown in the following code excerpt.
button.AddHandler(MouseLeftButtonDownEvent,
new MouseButtonEventHandler(Button_MouseLeftButtonDown),
true);
I've written a test application, having a button, and added a handler for each of the Events. When an event handler is invoked, it writes some information into a text block.
When I click the button, all three event handlers are invoked.
When I add e.Handled = true to the Preview...-EventHandler, only this event handler is invoked. Even the Mouse...-EventHandler is not raised, although I've set UIElement.AddHandler handledEventsToo to true.
When I add e.Handled = true to the Mouse...-EventHandler, all three event handlers are invoked.
That does not make any sense to me. Mouse...-EventHandlers do not affect the Click-EventHandlers, but Preview...-EventHandlers affect both Mouse...- and Click-EventHandlers.
And even 'forcing' to handle an event failed for the Mouse...-EventHandler.
Actually, I've never thought that event handlers of different types could affect each others. What I understood is that if I've got a Preview...-Event and a Click-Event, that these are independent.
So, what am I missing?
Here's the pretty simple sample code:
XAML:
<DockPanel>
<Border x:Name="border" DockPanel.Dock="Top" Height="50"
BorderBrush="Gray" BorderThickness="1">
<StackPanel x:Name="stackpanel" Background="LightGray"
Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="button" Width="Auto"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown">
Click Me
</Button>
</StackPanel>
</Border>
<Border DockPanel.Dock="Bottom" BorderBrush="Gray" BorderThickness="1">
<ScrollViewer>
<TextBlock x:Name="textBlock" TextWrapping="Wrap"/>
</ScrollViewer>
</Border>
</DockPanel>
Code-Behind:
public MainWindow()
{
InitializeComponent();
button.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
button.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click), true);
stackpanel.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Button_Click), true /*false*/ );
}
private void Output(object sender, RoutedEventArgs e)
{
textBlock.Text += "RoutedEvent: " + e.RoutedEvent + "\n";
textBlock.Text += "Sender: " + sender + "\n";
textBlock.Text += "Source: " + e.Source + "\n";
textBlock.Text += "OriginalSource: " + e.OriginalSource + "\n" + "\n";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// e.Handled = true;
Output(sender, e);
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// e.Handled = true;
Output(sender, e);
}
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Output(sender, e);
}

I've never thought that event handlers of different types could affect each other
You are mostly correct, as this is quite rare, but you can find your answers in the Preview Events page on MSDN. From the linked page:
For instance, a Windows Presentation Foundation (WPF) Button suppresses MouseLeftButtonDown and MouseLeftButtonDown bubbling events raised by the Button or its composite elements in favor of capturing the mouse and raising a Click event that is always raised by the Button itself. The event and its data still continue along the route, but because the Button marks the event data as Handled, only handlers for the event that specifically indicated they should act in the handledEventsToo case are invoked.
Furthermore, you said this:
When I add e.Handled = true to the Mouse...-EventHandler, all three event handlers are invoked
That is expected, as setting e.Handled in a bubbling event handler will do nothing... there is nothing that will read that value after the event has left the handler code. e.Handled is predominantly used in tunnelling event handlers to stop the events from any further routing. Again, from the linked page:
For input events specifically, Preview events also share event data instances with the equivalent bubbling event. If you use a Preview event class handler to mark the input event handled, the bubbling input event class handler will not be invoked. Or, if you use a Preview event instance handler to mark the event handled, handlers for the bubbling event will not typically be invoked.

Related

WPF UserControl Event Handler Memory Loss

I have a form which references a UserControl
<Label Grid.Row="1" Grid.Column="2" >Item Category</Label>
<PO:ItemCategorySelector Grid.Row="1" Grid.Column="3" x:Name="usrItemCategoryId" SelectedValueChanged="usrItemCategoryId_SelectedValueChanged"
IsOptional="True" Width="250" />
The event is defined as follows:
#region events
public delegate void SelectedValueChangedDelegate(object sender, SelectedValueChangedEventArgs e);
protected event SelectedValueChangedDelegate _SelectedValueChanged;
public event SelectedValueChangedDelegate SelectedValueChanged
{
add
{
if (this._SelectedValueChanged == null)
this._SelectedValueChanged += value;
}
remove
{
if (this._SelectedValueChanged != null)
this._SelectedValueChanged -= value;
}
}
protected void RaiseSelectedValueChanged(modItemCategory pItemCategory)
{
if (this._SelectedValueChanged != null)
{
var evt = new SelectedValueChangedEventArgs(pItemCategory);
this._SelectedValueChanged(this, evt);
}
}
endregion
I have put break points on the Add and Remove properties of the event.
When the form loads the Add property triggers.
When I close the form the Remove property never triggers.
This must contribute to the memory leaks I am finding.
Surely if a control is defined in XAML and it links in the event handler it should be responsible for removing that handler? If not how would you set about removing it?
What am I missing?
Surely if a control is defined in XAML and it links in the event handler it should be responsible for removing that handler?
No, there is no one unsubscribing from the event handler for you.
But this shouldn't be an issue as long as the publisher of the event (the ItemCategorySelector) and the subscriber of the event (the UserControl) have equal lifetimes.
The ItemCategorySelector instance will be eligible for garbage collection as soon as the parent UserControl instance is. So if you are experiencing any memory leaks, it is probably not because of this event handler.
If not how would you set about removing it?
You could for example handle the Unloaded event and remove the event handler using the -= syntax:
private void usrItemCategoryId_Unloaded(object sender, RoutedEventArgs e)
{
usrItemCategoryId.SelectedValueChanged -= usrItemCategoryId_SelectedValueChanged;
}

WPF ListView and ScrollViewer hide MouseLeftButtonDown

To demostrate the problem I have this Xaml:
<DockPanel MouseLeftButtonDown="DockPanel_MouseLeftButtonDown" MouseLeftButtonUp="DockPanel_MouseLeftButtonUp">
<ListView>
<ListViewItem>ListViewItem</ListViewItem>
</ListView>
<TextBlock>TextBlock</TextBlock>
</DockPanel>
and the event handlers are :
private void DockPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("DockPanel_MouseLeftButtonDown");
}
private void DockPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("DockPanel_MouseLeftButtonUp");
}
When you run the app and click on the words TextBlock you get MouseDown fired followed by MouseUp. So far so good. But when you click on the words ListViewItem only MouseUp is fired. Same problem for ScrollViewer (List view includes it so I am guessing it's the same problem).
Does anybody know why and if this can be fixed.
By fixed I mean get it to fire not try to use another event or another mechanism all together.
First the problem:
As suspected the problem is in ScrollViewer: http://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/ScrollViewer.cs,488ab4a977a015eb
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
As you can see it sets MouseButtonEventArgs.Handled to true which stops the bubbling of the event.
Now the solution - it is in the way you add the handler:
MyListView.AddHandler(
ListView.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(ListView_MouseLeftButtonDown),
true);
Note the last parameter (true) it causes the handler to be invoked even if the EventArgs.Hanlded was set to true.
Then you can reset it:
private void ListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = false;
}
I had somewhat similar situation when ScrollViewer was blocking my MouseLeftButtonDown event. I had a content control wrapped into ScrollViewer:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ContentControl x:Name="Details" />
</ScrollViewer>
and this was inside of Popup which had a drag/drop behavior. So, because my behavior was not receiving this event, it did not work. When I added IsHitTestVisible="True" to ScrollViewer, my behavior started to work, but of course my ContentControl was not responding to any clicks. Then I saw this:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
and tried to add Focusable="False" to exclude ScrollViewer from my click - it works. My behavior works and controls inside of ContentControl are getting all mouse events.
<ScrollViewer VerticalScrollBarVisibility="Auto" Focusable="False">
<ContentControl x:Name="Details" />
</ScrollViewer>
Hope it will help somebody.

How to stop FocusManager from Moving the focus outside an open Popup when using IsFocusScope option

I have two controls a ToolBar and a TextBox. The ToolBar has a Button which opens a Popup with another Button in it.
Current behavior: if i click inside the TextBox and it becomes focused and then click the button from ToolBar which opens a Popup the TextBox is still focused and receives all Keyboard input.
Now i know this is the default behavior for items inside a FocusScope which the ToolBar is, but i don't need this behavior when a popup is open. How can i avoid it ?
Here is the example:
<Window x:Class="WpfApplication67.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
</Window.Resources>
<StackPanel HorizontalAlignment="Left" Width="400">
<ToolBar>
<ToggleButton Name="openButton">Open Popup</ToggleButton>
<Popup Focusable="True"
Placement="Right"
StaysOpen="False"
IsOpen="{Binding IsChecked, ElementName=openButton, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=openButton}">
<StackPanel
Width="100"
Height="100"
Background="DarkGray"
Focusable="True">
<Button>More</Button>
</StackPanel>
</Popup>
</ToolBar>
<TextBox Text="Set focus on this..." />
</StackPanel>
EDIT:
I'm striving to find an explanation about Who moves the focus on button click inside a nested FocusScope and How i can stop some Buttons (like the one inside a Popup) from doing it.
You mainly have three requirements (correct me if am wrong):
If pop up is opened, focus should be inside popUp i.e. on StackPanel.
On close of popUp, focus should retained back to textBox.
When button inside popUp is clicked, focus should not leave the popUp.
Let's pick these requirements one by one.
If pop up is opened, focus should be inside popUp i.e. on StackPanel.
Like I mentioned in the comments, put focus on stackPanel on Opened event of PopUp:
private void Popup_Opened(object sender, EventArgs e)
{
stackPanel.Focus();
}
On close of popUp, focus should retained back to textBox.
Hook Closed event and put focus back on TextBox:
private void Popup_Closed(object sender, EventArgs e)
{
textBox.Focus();
}
When button inside popUp is clicked, focus should not leave the popUp.
Now, comes the tricky part. As mentioned in the comments as soon as you click on button inside popUp, focus is moved outside of PopUp.
What you can do prevent is to attach a handler to PreviewLostKeyboardFocus event of stackPanel. In handler check for condition if keyBoard focus is within popUp, set e.Handled = true so that event gets handled here only and no bubble event is raised which will force the keyboard focus outside of stackPanel.
That being said in case you have another TextBox inside stackPanel besies button, handling event won't allow you to move focus within popUp as well. To avoid such situations you can check if new focused element doesn't belong within stackPanel then only handle the event.
Here's the code to achieve that (add handler on PreviewLostKeyboardFocus event of StackPanel):
private void stackPanel_PreviewLostKeyboardFocus(object sender,
KeyboardFocusChangedEventArgs e)
{
var newFocusedChild = e.NewFocus as FrameworkElement;
bool isMovingWithinPanel = newFocusedChild != null
&& newFocusedChild.Parent == stackPanel;
// If keyboard focus is within stackPanel and new focused element is
// outside of stackPanel boundaries then only handle the event.
if (stackPanel.IsKeyboardFocusWithin && !isMovingWithinPanel)
e.Handled = true;
}
In this situation, there are two ways, both options have been added in the handler of Click event for openButton.
First
The easiest option, it is clear focus for your keyboard, like this:
private void openButton_Click(object sender, RoutedEventArgs e)
{
Keyboard.ClearFocus();
}
Second
A more universal method it is move Focus to the parent:
private void openButton_Click(object sender, RoutedEventArgs e)
{
FrameworkElement parent = (FrameworkElement)MyTextBox.Parent;
while (parent != null && parent is IInputElement && !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(MyTextBox);
FocusManager.SetFocusedElement(scope, parent as IInputElement);
}
For the latter case, I created attached behavior to make it more convenient to use, which can be founded here:
Set focus back to its parent?
Edit
If you want to be when you close Popup focus back to the TextBox, then add handlers of events Opened and Closed for Popup like this:
private void MyPopup_Opened(object sender, EventArgs e)
{
Keyboard.ClearFocus();
StackPanelInPopup.Focus();
}
private void MyPopup_Closed(object sender, EventArgs e)
{
MyTextBox.Focus();
}

Prevent hyperlink Click event from bubbling up

I'm designing a windows Phone app. I have a Hyperlink object in a RichTextBox, in a Grid. The Grid had a Tap event, and the Hyperlink has a Click event.
Clicking the Hyperlink also raises the parent Grid's Tap event. How can I prevent this?
I would use e.Handled in the Click handler, but RoutedEventArgs do not have a Handled property in Silverlight for Windows Phone... I also tried walking the logical tree to look for the original source, but the click appears to originate from a MS.Internal.RichTextBoxView control (e.OriginalSource)...
I don't think there is any good way from within the Click handler itself. The following state management can work though:
XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Tap="ContentPanel_Tap_1">
<RichTextBox Tap="RichTextBox_Tap_1">
<Paragraph>
fdsfdfdf
<Hyperlink Click="Hyperlink_Click_1">fdsfdsfsaf</Hyperlink>
fsdfsdfa
</Paragraph>
</RichTextBox>
</Grid>
and the code:
bool RtbTapHandled = false;
private void Hyperlink_Click_1(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Hyperlink");
RtbTapHandled = true;
}
private void RichTextBox_Tap_1(object sender, System.Windows.Input.GestureEventArgs e)
{
if (RtbTapHandled)
{
e.Handled = true;
}
RtbTapHandled = false;
System.Diagnostics.Debug.WriteLine("RTB_Tap");
}
private void ContentPanel_Tap_1(object sender, System.Windows.Input.GestureEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Content_Tap");
}
In this case if you click on the RichTextBox you'll get callbacks from both RichTextBox_Tap_1 and ContentPanel_Tap_1, but if you click on the Hyperlink you'll get Hyperlink_Click_1 and RichTextBox_Tap_1 though it'll be handled at that level and stopped.

Silverlight: How to Prevent Routing a MouseMove Event from a Child Canvas to Its Parent Canvas

I have my XAML code:
<Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightGray"
MouseLeftButtonUp="mainCanvas_MouseLeftButtonUp"
MouseMove="mainCanvas_MouseMove">
<Canvas x:Name="topCanvas" Width="200" Height="100" Background="LightBlue"
MouseLeftButtonUp="topCanvas_MouseLeftButtonUp"
MouseMove="topCanvas_MouseMove">
</Canvas>
</Canvas>
and its code behind:
private void topCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("topCanvas_MouseLeftButtonUp");
e.Handled = true; // This can prevent routing to the mainCanvas
}
private void mainCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("mainCanvas_MouseLeftButtonUp");
}
private void topCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("topCanvas_MouseMove");
// How to prevent routing to the mainCanvas?
// e.Handled = true does NOT exist in MouseEventArgs
}
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("mainCanvas_MouseMove");
}
My question is already in the comments.
How to prevent routing the MouseMove event from the topCanvas (the child canvas) to the mainCanvas (parent canvas)?
Thanks.
Peter
Try setting the IsHitTestVisible property of your Canvas. With that property set accordingly mouse events will go either "through" your control or will be caught by it.
Hope this is what you need.
You can try comparing e.OriginalSource in mainCanvas's MouseMove Event and exit the Sub if it wasn't originated from the mainCanvas.
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (sender != e.OriginalSource)
return;
}
In replying to your comment in a little more detail. According to the UIElement.MouseMove Event MSDN link.
Controls that inherit MouseMove can provide handling for the event
that acts as handler for all instances, by overriding the OnMouseMove
method. As with direct handling of the event, there is no Handled
property available, so OnMouseMove cannot be implemented in such a way
that it suppresses further handling of the event through the Handled
technique.
and this link states:
This event creates an alias for the Mouse.MouseMove attached event for
this class
Which brings us to this link on AttachedEvents which states.
Extensible Application Markup Language (XAML) defines a language
component and type of event called an attached event. The concept of
an attached event enables you to add a handler for a particular event
to an arbitrary element rather than to an element that actually
defines or inherits the event. In this case, neither the object
potentially raising the event nor the destination handling instance
defines or otherwise "owns" the event.
So as I see it, your only option is to code around it.
The functionality is called "Event Bubbling". You can stop it using below code:
jQuery:
event.stopPropagation();
Ref: http://api.jquery.com/event.stopPropagation/
You can also try below code:
e.stopPropagation(); //to prevent event from bubbling up
e.preventDefault(); //then cancel the event (if it's cancelable)
Ref: http://stackoverflow.com/questions/1967537/how-to-stop-event-bubbling-with-jquery-live
Thanks,
Ravi Verma

Resources