Can/Should MouseEnter event bubble? - wpf

Is it possible under any circumstances to get MouseEnter event bubbling?
MSDN says it's an attached event with Direct routing strategy, which technically excludes such possibility. I've a fairly complex control (in essence a hierarchy consisting of grids, stackpanels and content controls). I seem to get MouseEnter event propagated from bottom up, here's the debug dump taken from OnMouseEnter handler (I've the same custom control included at different levels of the hierarchy, which handles MouseEnter, so I have a central place for listening that event):
In: parent:s7b, timestamp:37989609
In: parent:s2, timestamp:37989609
In: parent:Root, timestamp:37989609
s7b, s2 and Root are FrameworkElement names and timestamp is e.Timestamp from MosueEnter event.
Provided that the Routing Strategy is Direct, how does WPF decide on event originator? Does it traverse the visual tree until the first FrameworkElement with attached MouseEnter event is found?
While I'm working on a minimalistic repro set for the problem, could anyone suggest what could cause the behaviour?
And here's the repro:
Create two custom controls, one is a contant control, another is event receiver.
1.1. MyContentControl
Code:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl),
new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
if (e.Source == e.OriginalSource
&& e.Source is MyContentControl)
{
Debug.Write(string.Format("mouseenter:{0}, timestamp:{1}\n",
(e.Source as MyContentControl).Name,
e.Timestamp));
}
base.OnMouseEnter(e);
}
}
XAML:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<StackPanel Orientation="Horizontal">
<local:MouseEventReceiver />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
1.2 MouseEventReceiver
Code:
public class MouseEventReceiver : Control
{
static MouseEventReceiver()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MouseEventReceiver),
new FrameworkPropertyMetadata(typeof(MouseEventReceiver)));
}
}
XAML:
<Style TargetType="{x:Type local:MouseEventReceiver}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="LightGray" Width="20" Height="20" Margin="5"></Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally the markup of my test harness:
XAML:
<Window x:Class="MouseTricks.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MouseTricks"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MyContentControl x:Name="c1">
<local:MyContentControl x:Name="c2">
<local:MyContentControl x:Name="c3" />
</local:MyContentControl>
</local:MyContentControl>
</Grid>
</Window>
In order to reproduce the problem just hover over the right most gray square and watch the Debug Output window, you'll see three entries in there, while I'm expecting just one.
Cheers.

Perhaps a more detailed description will help. In the MSDN article on Mouse.MouseEnter the following quote is made:
Although this event is used to track when the mouse enters an element, it is also reporting the IsMouseOver property has changed from false to true on this element
MSDN says that Mouse.MouseEnter fires when IsMouseOver changes from false to true. Looking at the MSDN article for IsMouseOver the following quote is made:
Gets a value that indicates whether the mouse pointer is located over this element (including visual children elements that are inside its bounds)
As we both agree, the null background does not support interaction. There are a lot of caveats to the null background issue with regards to IsMouseOver, but it is obvious from practical application that this value does not get switched for a null background. However, the definition does say that if the mouse is "located over" any visual child within the bounds of the element then IsMouseOver will change barring several strange caveats. However, the null background is not one of these caveats.
A quick look at the visual tree of your control using the snoop utility or VisualTreeHelper shows that all three gray grids are the visual children of c1, the two rightmost grids are visual children of c2, and the rightmost grid is a visual child of c3. This is as would be expected since all of your content controls are nested within each other.
By monitoring the IsMouseOver for c1 property you can easily see that when the mouse touches a gray square the property value changes to true. You can verify this by adding a callback to the main window's mouse move event. I used the following callback:
private void MouseMove_Callback(Object sender, MouseEventArgs e)
{
if (c1.IsMouseOver)
MessageBox.Show("Mouse is Over c1!");
}
You will notice that no matter which of the three gray squares that you are over the IsMouseOver for c1 is set to true. This indicates that IsMouseOver changes to true for c1 when it is over any of the three squares and so the claims that MSDN makes are true. MouseEnter should and does get fired for c1 no matter which gray square you touch since all three gray squares are in c1s visual tree and are not eliminated from mouse hit testing by a caveat (such as the null background caveat).
The MouseEnter event is responding as a direct event in your application just as MSDN claims.

Since this is a complex control it seems likely that when you are entering the Root element with the mouse you are also entering s7b and s2 at the same time. Since all three elements are registered for the MouseEnter event they should all respond at exactly the same time if it is possible for the mouse to enter all three elements simultaneously.
It probably appears that the event is bubbling up the visual tree because you happen to have MouseEnter registered for a line of visual parents of similar size. If I define a Button in a StackPanel with the button stretching to fill the StackPanel and register both for the MouseEnter event then whenever the mouse enters the Button it will by default also be entering the parent (the StackPanel). In this case it may look like the event is bubbling up the visual tree when in fact it is just a direct event for two separate elements that is occurring simultaneously.
If you are creating a complex control then usually you would want one MouseEnter callback for the entire control or specific MouseEnter callbacks for specific pieces of the control. Are you sure that you need callbacks for the entire control as well as individual parts of the control?
-Edit
Just saw your new post. I tried your code and I noticed that the content MyContentControl instances are all nested. Since the MyContentControl class derives from content control the controls are being stretched to fit the available space. You can see this by adding a border property to the MyContentControl class. Since the background of MyContentControl is null by default MouseEnter only gets fired when one of the gray boxes is touched.
The first MyContentControl creates a horizontal Stackpanel and adds the gray box and then a content presenter. Anything to the right of the grid with the first gray box will automatically be in c2 and/or c3 because the content presenter from c1 will be stretched to fit the size of the window which has a fixed height and width. This is why when you hover over c2 you get the MouseEnter for c1 and c2 because when the gray box is touched the mouse has entered the content presenter of c1 and the mouse has also entered the gray box of c2. Similar logic can be used to understand the case for c3.

Mouse transparent controls (MTC) (I'd tend to call them layout controls) having mouse opaque children (MOC) can't handle mouse events correctly.
I could be wrong, but it looks like a bug to me. I can guess that the culprit is the fact that MTCs are incapable of handling mouse input but pretend to do so rather inconsistantly.
Due to the virtue of attached events, MTCs become Source & OriginalSource of mouse events, also their IsMouseOver gets set to true, which doesn't get on well with other parts of the system.
The workaround is - do subscribe only mouse opaque parts of your controls to mouse events. Sounds horrible at first glance, but at you shouldn't lose much of flexibility provided you use commands.
Any suggestions are highly appreciated.

Related

Catching right mouse click on user control level

<UserControl>
<Grid>
<!-- multiple controls here -->
</Grid>
</UserControl>
As the above example says, there are multiple MS / 3rd party controls hosted on UserControl. I need to catch mouse right click over the UserControl (or any of its child controls). It seems that PreviewMouseRightButtonUp event is not fired when clicked on some of the 3rd party controls inside the UserControl. As per documentation, PreviewMouseRightButtonUp is not a tunneling event, but a direct event, so it is possible that some 3d party controls do not notify the subscribers about this event.
I have also tried to add handler to this event, but still no result
AddHandler(UserControl.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.PreviewMouseRightButtonUpEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.MouseRightButtonDownEvent, new RoutedEventHandler(GetHandledToo), true);
AddHandler(UserControl.MouseRightButtonUpEvent, new RoutedEventHandler(GetHandledToo), true);
So, is there a way to always catch the right mouse button event on the user control level, no matter if it is being marked as handled or not?
Edit: I have found where the problem lies. The problem lies in the ContentControl which has not yet evaluated its content. Example:
<ContentControl x:Name="chart" Content="{Binding DiagramScreen}" />
ContentControl recolves its structure through DataTemplate. If DiagramScreen is NULL, the ContentControl's content is not yet created. This means that the space that this ContentControl occupies in UserControl is not responding to Mouse events. How Can I make ContentControl respond to mouse events, even if its content is NULL?
I think if it is handled you can't get it further for bubbling event. A workaround is to pass your host object to your user control. After that, you can call whatever method you want from your host object. It's ugly but I think it will work

Focus handling with Controls inside ControlTemplate

What is the proper way to manage the focus when you write your own control which has controls inside its control template?
Let's say we have a CustomControl with two TextBoxes in its ControlTemplate. As user and developer I would expect following behaviour:
When Calling customControl.Focus(), then the first TextBox in the template should receive focus.
Moving focus forward and backward using Tab / Shift+Tab is expected to work which means:
2.1 If the user presses Tab having focus in a control before the customControl, then focus should move to the first textbox of customControl.
2.2 If the user presses Shift+Tab having focus in the first textbox of custom control, then focus should move to any control before the customControl.
Following I tried already:
a. Setting the CustomControl to Focusable=false enables condition 2. but disables condition 1.
b. On StackOverflow the question how to move focus to a control inside the control template appears many times. Always proposing following solution:
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="PART_TextBox1" Property="FocusManager.FocusedElement" Value="{Binding ElementName=PART_TextBox1}" />
</Trigger>
This solution enables condition 1 and 2.1 but destroys condition 2.2.
Thanks for your help :)
You could go with the second option (the trigger on IsFocused property) and keep condition 2.2 satisfied by simply setting the IsTabStop property of your CustomControl to False. The big flaw in this solution though is that it can be easily undone by setting the IsTabStop property back to True.
The other way to achieve Your goal would be to drop the trigger and deal with focus changes in the control's code-behind like this:
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (e.NewFocus == this)
{
if (object.ReferenceEquals(e.OldFocus, Template.FindName("PART_TextBox1", this)))
MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
else
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
I'd also suggest setting properties IsTabStop={TemplateBinding IsTabStop} and TabIndex={TemplateBinding TabIndex} on any focusable control in Your template, so that when any of these properties is set on your control, it still maintains the inner navigation order and prevents/allows navigating with TAB key to each of the inner controls.

How to set logical focus without giving keyboard focus?

How can I in code define the logical focus of a container, or focused child, WITHOUT giving it keyboard focus ?
I just want to control which child will get the focus when the control will get focus through Tab key or clicking on part of the container that does not hit a child, but not give it (or steal) actual focus if it doesn't already have it.
And I also want that selecting a specific child through a keyboard gesture or clicking on it with the mouse is still possible.
I understand the difference in WPF between Keyboard Focus and Logical Focus, and the fact that for an element, having Keyboard Focus implies also having logical focus, but having logical focus does not imply that the element have keyboard focus.
I also understand attached property FocusManager.FocusedElement defines which element has logical focus in the visual tree starting at the element defining this property.
I’ve realized that this property is not used only when FocusManager.IsFocusScope is set to true, but also for containers such as GroupBox.
I’ve made many attempts, tons of searches and reading on WPF focus topic, but with no success so far, I don't understand what I'm missing:
Calling FocusManager.SetFocusedElement also give keyboard focus, and it I temporarily change the property Focusable of my child element to false just before, it only works the very first time when no child had focus before, but not after a child got focus
Handling events GotKeyboardFocus and PreviewGotKeyboardFocus at element or container to override initial focused element doesn’t work either, since I cannot tell whether the focus was obtained through the mouse or the keyboard, and whether focus got directly set to a child element or indirectly through the container.
An example to illustrate what I’m trying to achieve: I’ve a simple group of RadioButtons, and I want to control dynamically in code which option will get focus when user will “tab” to move focus to this GroupBox (typically the option that has isChecked=true).
<GroupBox Header="Options" Name="myGroupBox"
KeyboardNavigation.TabNavigation="Once"
KeyboardNavigation. DirectionalNavigation="Cycle" >
<StackPanel>
<RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
<RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
<RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
</StackPanel>
</GroupBox>
A final comment, I know how to implement a dynamic list of options using a ListBox, binding selectedItem of the list to a property of the data context, and then through styles and templates on ListBoxItem bind IsChecked property of the RadioButton in item template to IsSelcted property of its parent ListBoxItem, and it works, but for my specific case, I need to keep my RadioButtons directly bound to properties of my data context, I can’t bind them to IsSelected property of the list at the same time.
I know this is an old question but hopefully this answer will help others with a similar problem.
If I understand the problem correctly, you can achieve the desired behaviour by setting FocusManager.IsFocusScope="True" on the GroupBox, and hook up an event handler for the RadioButton.Checked event that sets the Logical Focus to the sender of the event:
Xaml:
<GroupBox Header="Options" Name="myGroupBox"
FocusManager.IsFocusScope="True"
RadioButton.Checked="MyGroupBox_OnChecked"
KeyboardNavigation.TabNavigation="Once"
KeyboardNavigation.DirectionalNavigation="Cycle">
<StackPanel>
<RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
<RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
<RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
</StackPanel>
</GroupBox>
Code behind:
private void MyGroupBox_OnChecked(object sender, RoutedEventArgs e)
{
var radioB = sender as RadioButton;
if (radioB != null)
FocusManager.SetFocusedElement(myGroupBox, radioB);
}
As the Document remarked, FocusManager.SetFocusedElement will give the specified element logical focus in the specified focus scope and will attempt to give the element keyboard focus.
I encountered the same problem, and I feel this attempt to give the element keyboard focus is rather not predictable. In some cases, the element which gets the logical focus will also get the keyboard focus. But in some other cases, it won't. I have not yet figured out the exact mechanism behind.
If the official documents say so, I reckon there may not be an "official" way to implement what you want. For now I am doing this by just invoking Keyboard.Focus upon the element you would like to maintain the keyboard focus after setting the logical focus.
An illustration of what I am explaining is as follows:
private void SomeMember(){
FocusManager.SetFocusedElement(focusScope, logicalFocus);
Keyboard.Focus(controlMaintainingKeyboardFocus);
}

Silverlight ListBox: How to find out if user is currently dragging the scrollbar slider?

Scenario: I have a ListBox, containing a WrapPanel from the Silverlight Toolkit as ItemsPanel:
<ListBox x:Name="listBoxFaelle">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate>
<Grid>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ListBox.Template>
</ListBox>
This ListBox gets populated automatically with the results of a RIA service call every 60 seconds. While the application is waiting for the LoadOperation to complete, it displays the standard BusyIndicator that is part of the Silverlight Business Application template.
This works fine as long as the user is not dragging the scrollbar slider of the ListBox when the Timer Event fires.
Problem: If the user is dragging the scrollbar slider when the BusyIndicator is being displayed, the scrollbar doesn’t work anymore, afterwards. It looks like the mouse pointer remains captured to the slider even if the left mouse button is released. I assume this happens because the BusyIndicator temporarily takes away the focus from the slider while it is being dragged.
Question: Is there a way to find out if the scrollbar slider of the ListBox is currently being pushed?
As far as I know there is no status/property to find out if the slider is being pushed.
That said you could try to keep track of it yourself by handling MouseLeftButtonDown and Up events but you might have to consider other events as well depending on your scenario (mouse wheel?)
MouseLeftButtonUp Occurs when the left mouse button is released (or the tip of the stylus is removed from the tablet) while the mouse (or the stylus) is over a UIElement (or while a UIElement holds mouse capture). (Inherited from UIElement.)
It might also be possible to solve this the other way around by releasing the MouseCapture (if that is causing the problem) by using ReleaseMouseCapture
Again, it depends on what you want to happen from the user's point of view: skip the loading of the data (this is what I would prefer because I do not like the content to be changing when I am scrolling) or reset the listbox and scroll to the top (ugly in my opinion)

WPF: Making child elements' MinWidth/MinHeight constrain the Window

I have a WPF Window that contains a UserControl with a MinWidth and MinHeight. How can I prevent the user from resizing the Window down to a point where that UserControl's minimum size is violated?
Here's a simplified version of a Window I'm working on. My real app's UserControl is replaced here by a Border:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="OK"/>
<Button Content="Cancel"/>
</StackPanel>
<Border BorderBrush="Green" BorderThickness="10"
MinWidth="200" MinHeight="150"/>
</DockPanel>
</Window>
If I shrink the window small enough, the Border's right and bottom edges are cut off. I want to prevent the window from getting that small -- I want my window's minimum size to be exactly the point at which the Border is at its minimum size. Some frameworks (like the Delphi VCL) automatically aggregate child controls' minimum sizes up to the window; I expected WPF to do the same, but clearly it does not.
I could always explicitly set the Window's MinWidth and MinHeight, but to calculate those correctly, I would have to factor in the Buttons' ActualHeight, which would mean waiting for at least one layout pass (or calling Measure manually). Messy.
Is there any better way to keep the Window from resizing too small for its content?
The simplest way I've found is to tell the Window to size to its content:
<Window ... SizeToContent="WidthAndHeight" ...>
and then, once it's done sizing (which will take the child elements' MinWidth and MinHeight into account), run some code that sets MinWidth and MinHeight to the window's ActualWidth and ActualHeight. It's also a good idea to turn SizeToContent back off at this point, lest the window resize when its content changes.
The next question is, where to put this code? I finally settled on OnSourceInitialized:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
MinWidth = ActualWidth;
MinHeight = ActualHeight;
ClearValue(SizeToContentProperty);
}
I also tried the Loaded event, but in my case, that was too soon -- Loaded happens before databindings have been evaluated, and I had databindings that affected my layout (a Label with a binding for its Content -- its size changed after the binding took effect). Moving the code into OnSourceInitialized, which fires after databinding, corrected the problem.
(There were also other events that fired after binding, but before the window was shown -- SizeChanged and LayoutUpdated -- but they both fire multiple times as the window is shown, and again later if the user resizes the window; OnSourceInitialized only fires once, which I consider ideal.)
Have you tried using an ElementName style binding from the Window to the UserControl at hand? It seems like it would be feasible to bind the Window's MinWidth/Height to those of the Button. Still not pretty, but it shouldn't require extra passes once the binding is in place and will adapt if you change the values on the Button.

Resources