In my application, when a user attempts to click a slider which is on the main window, while a popup control is open, the popup control steals the mouse down event.
This results in the slider not responding to the mouse down event correctly.
(it seems to get focus and move to an incorrect location)
I found that the that the "OnPreviewMouseLeftButtonDown" in the slider does not fire when popup's "StaysOpen" property is false (and the popup is open),
and does fire when its true (or when the popup is closed).
I was wondering if someone has found a solution for this issue.
I encountered these type of issues in other controls in my application in various contexts, So I would prefer a more general solution rather than just solving this for the slider.
Sample code:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Root"
Title="MainWindow" Height="350" Width="525">
<Grid Height="130" Width="300">
<Button Width="40" Height="40" Click="ButtonBase_OnClick" HorizontalAlignment="Left" VerticalAlignment="Top"></Button>
<Popup StaysOpen="False" IsOpen="{Binding ElementName=Root, Path=IsOpen}" Width="100" Height="100"
HorizontalAlignment="Center" VerticalAlignment="Center" Placement="Center">
<Grid Background="Black">
<TextBlock Text="hello"></TextBlock>
</Grid>
</Popup>
<Slider Width="200" IsMoveToPointEnabled="True" VerticalAlignment="Bottom"></Slider>
</Grid>
Thanks ahead,
Yotam
This happens because PreviewMouseDown (and it's derivates) (from the base class UIElement) has a default RoutingStrategy.Direct.
Direct - The routed event does not route through an element tree, but does support other routed event capabilities such as class handling, EventTrigger or EventSetter.
This is the source code of the event taken from ReferenceSource.
public static readonly RoutedEvent PreviewMouseLeftButtonDownEvent =
EventManager.RegisterRoutedEvent(
"PreviewMouseLeftButtonDown",
RoutingStrategy.Direct,
typeof(MouseButtonEventHandler),
_typeofThis);
And here is what happens in the Popup:
private void OnPreviewMouseButton(MouseButtonEventArgs e)
{
// We should only react to mouse buttons if we are in an auto close mode (where we have capture)
if (_cacheValid[(int)CacheBits.CaptureEngaged] && !StaysOpen)
{
Debug.Assert( Mouse.Captured == _popupRoot.Value, "_cacheValid[(int)CacheBits.CaptureEngaged] == true but Mouse.Captured != _popupRoot");
// If we got a mouse press/release and the mouse isn't on the popup (popup root), dismiss.
// When captured to subtree, source will be the captured element for events outside the popup.
if (_popupRoot.Value != null && e.OriginalSource == _popupRoot.Value)
{
// When we have capture we will get all mouse button up/down messages.
// We should close if the press was outside. The MouseButtonEventArgs don't tell whether we get this
// message because we have capture or if it was legit, so we have to do a hit test.
if (_popupRoot.Value.InputHitTest(e.GetPosition(_popupRoot.Value)) == null)
{
// The hit test didn't find any element; that means the click happened outside the popup.
SetCurrentValueInternal(IsOpenProperty, BooleanBoxes.FalseBox);
}
}
}
}
So it was designed to work this way, and you should likely not use OnPreviewMouseDown for whatever you are trying to accomplish here.
In my application, when a user attempts to click a slider which is on the main window, while a popup control is open, the popup control steals the mouse down event
While your description is not completely correct, that is the normal behaviour of any Popup control. The reason that this occurs is because the Popup control has focus and so it is listening out for the Click event even if it occurs outside the bounds of the Popup. Think about this logically now... if it didn't do this, how would it know when to close? You will find the same behaviour from the Popup control used in a ComboBox.
There is a workaround to achieve the behavior you require, Set 'IsHitTestVisible = True' for the Slider control you are use.
PS:
Set IsHitTestVisible = True, only when the Popup is Open - False otherwise.
Related
I'm not using any external libraries here, just plain WPF.
I have a DataGrid with a custom DataGridColumnHeader. This column header contains a ToggleButton to toggle a Popup. Inside the popup there is a TextBox. The problem I'm having is that doubleclicking inside the TextBox raises the MouseDoubleClick event on the DataGrid. Here's a simplified version containing numbered comments I will refer to afterwards
<Window x:Class="PopupsAreWeird.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PopupsAreWeird" xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<!-- 1) This eventhandler is never called -->
<Grid Control.MouseDoubleClick="Grid_MouseDoubleClick_1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" />
<ToggleButton x:Name="openToggle" Grid.Column="1" Content="Open" />
<Popup IsOpen="{Binding ElementName=openToggle, Path=IsChecked}" StaysOpen="True">
<!-- 2) This eventhandler is always called, and the problem I am having is there regardless of whether I set e.Handled = true in this handler or not -->
<TextBox Width="200" MouseDoubleClick="TextBox_MouseDoubleClick" />
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical">
<!-- 3) This eventhandler is always called, but never should be -->
<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="Header 1" Width="200" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>
1) I do not understand why this handler is never called. This was meant to set e.Handled to true to stop bubbling upwards to the DataGrid. While Grid does not have a definition for MouseDoubleClick, it is my understanding that I can attach eventhandlers for any event to any element (like attaching ButtonBase.Click to a Panel element). Is this not true, or is there any special case here I am missing?
3) I want to avoid the double click event bubbling to here, but even if I set e.Handled to true in eventhandler 2, this eventhandler is called, and in that handler, e.Handled is false. I assume the reason is that the Popup is defined inside of DataGridColumnHeader, and for some weird reason 2 events are being raised, one for the tree of the popup, and one for the tree that contains the Popup element, but that seems a bit nonsensical.
I know Popup is a sort of weird thing in WPF, but this seems like I'm missing something obvious. Is there any way to achieve what I want, i.e. not have events (or at least the MouseDoubleClick event) bubble up to the DataGrid?
Thanks in advance,
David
1) You are right, You can attach a handler for a routed event to any UIElement. But indeed the Popup is a special case. The Popup is a special control that behaves like Window. It can popup everywhere on the screen, always rendered top-most and is not necessarily bound to the application itself. That's why its visual tree is detached from the application's visual tree. Popup.Child will be a separate isolated visual tree. Microsoft Docs: Popup and the Visual Tree.
Since routed events traverse the visual tree to be handled by any node, it makes sense that bubbling/tunneling routed events inside the Popup will stop/start at the root of this isolated tree. So routed events that are originated in the Popup cannot be handled outside the Popup.
3) Short version: Control.MouseDoubleClick (and the preview version) is a special event that behaves different. This event is raised on each UIElement on the route when the event traverses the visual tree. So setting Handled to true has no effect.
To solve your problem, you should either handle UIElement.PreviewMousLeftButtonDown and check if the MouseButtonEventArgs.ClickCount equals 2 to detect a double click and then set Handled = true
or check if the type of the sender or RoutedEventArgs.Source is not TextBox before handling it (explicit event filtering).
"Although this routed event seems to follow a bubbling route through
an element tree, it actually is a direct routed event that is raised
along the element tree by each UIElement. If you set the Handled
property to true in a MouseDoubleClick event handler, subsequent
MouseDoubleClick events along the route will occur with Handled set to
false. This is a higher-level event for control consumers who want to
be notified when the user double-clicks the control and to handle the
event in an application.
Control authors who want to handle mouse double clicks should use the
MouseLeftButtonDown event when ClickCount is equal to two. This will
cause the state of Handled to propagate appropriately in the case
where another element in the element tree handles the event.
The Control class defines the PreviewMouseDoubleClick and
MouseDoubleClick events, but not corresponding single-click events. To
see if the user has clicked the control once, handle the MouseDown
event (or one of its counterparts) and check whether the ClickCount
property value is 1."
Microsoft Docs: Control.MouseDoubleClick
I have two buttons that show Flyouts when clicked. I am displaying them the same way as demonstrated in the XAML UI Basics sample:
private void ButtonTapped(object sender, TappedRoutedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
FlyoutBase.ShowAttachedFlyout(element);
}
}
My problem is that if Button 1's flyout is open, the next tap on the screen closes the flyout. This is fine, but if the next tap happens to be on Button 2, I want the button's tap event to be fired and open its flyout. Instead, the button doesn't register a tap at all and closes Button 1's flyout.
This results in needing to tap two times - one to dismiss the Button 1's flyout, and a second to show Button 2's flyout.
In other words:
Current Flow:
Tap Button 1
Button 1's Flyout is opened
Tap Button 2
Button 1's flyout is closed, (Button 2, nor Page registers the tap)
Tap Button 2
Now Button 2's flyout is opened
What I'm Looking For:
Tap Button 1
Button 1's Flyout is opened
Tap Button 2
Button 1's flyout is closed, Button 2's flyout opens.
How can I do this? I've tried intercepting the Tapped event for the page, but when the flyout is open, it seems to intercept the Tapped event so it can be used for the Flyout's light dismissal
Would overriding the style of the Flyout, or the FlyoutPresenterStyle help me here? Or perhaps opening the Flyout in a more MVVM-ish way which would allow me for finer control on how the Flyout is opened/closed?
I'm not sure how to get around this!
Here is something I found on Microsoft documents:
When dismissing with a tap, this gesture is typically absorbed and not passed on to the UI underneath. For example, if there’s a button visible behind an open flyout, the user’s first tap dismisses the flyout but does not activate this button. Pressing the button requires a second tap.
You can change this behaviour by designating the button as an input pass-through element for the flyout. The flyout will close as a result of the light dismiss actions described above and will also pass the tap event to its designated OverlayInputPassThroughElement. Consider adopting this behaviour to speed up user interactions on functionally similar items. If your app has a favourites collection and each item in the collection includes an attached flyout, it's reasonable to expect that users may want to interact with multiple flyouts in rapid succession.
[!NOTE] Be careful not to designate an overlay input pass-through element which results in a destructive action. Users have become habituated to discreet light dismiss actions which do not activate primary UI. Close, Delete or similarly destructive buttons should not activate on light dismiss to avoid the unexpected and disruptive behaviour.
In the following example, all three buttons inside FavoritesBar will be activated on the first tap.
<Page>
<Page.Resources>
<Flyout x:Name="TravelFlyout" x:Key="TravelFlyout"
OverlayInputPassThroughElement="{x:Bind FavoritesBar}">
<StackPanel>
<HyperlinkButton Content="Washington Trails Association"/>
<HyperlinkButton Content="Washington Cascades - Go Northwest! A Travel Guide"/>
</StackPanel>
</Flyout>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="FavoritesBar" Orientation="Horizontal">
<HyperlinkButton x:Name="PageLinkBtn">Bing</HyperlinkButton>
<Button x:Name="Folder1" Content="Travel" Flyout="{StaticResource TravelFlyout}"/>
<Button x:Name="Folder2" Content="Entertainment" Click="Folder2_Click"/>
</StackPanel>
<ScrollViewer Grid.Row="1">
<WebView x:Name="WebContent"/>
</ScrollViewer>
</Grid>
private void Folder2_Click(object sender, RoutedEventArgs e){
Flyout flyout = new Flyout();
flyout.OverlayInputPassThroughElement = FavoritesBar;
flyout.ShowAt(sender as FrameworkElement);}
Could You please help me, why GotFocus and LostFocusa event doesn't fired when I Click to image and then to textbox?
My XAML:
<Window x:Class="imageclick.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">
<Grid>
<StackPanel>
<Image Source="Untitled.png" GotFocus="GF" LostFocus="LF" Focusable="True"></Image>
<TextBox ></TextBox>
</StackPanel>
</Grid>
</Window>
I could not understand why GotFocus/LostFocus event never fired
Thanks in advance
Update: When I set the tabindex, when the tab reached the image event fired, but I could not reach with mouse click
Image isn't a Control. Only Controls can get focus.Instead of GotFocus and LostFocus use MouseEnter and MouseLeave events,
<StackPanel>
<Image Stretch="Uniform" Source="Untitled.png" Height="410" MouseEnter="Image_MouseEnter" MouseLeave="Image_MouseLeave"></Image>
<TextBox Height="65"></TextBox>
</StackPanel>
According to MSDN, UIElement.GotFocus event occurs when this element gets logical focus.
And logical focus differs from keyboard focus, it is raised when the value of the IsFocused property of an element in the route is changed from false to true.
So, in order to achieve it through mouse clicks, need to handle the respective mouse button events or simply handle MouseDown and set the focus to the sender.
private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is Image)
{
(sender as Image).Focus();
}
}
This will set the IsFocused property of the Image to true.
I have a situation where I would want to use WFP WebBrowser, but when the user presses a button something happens; however after WebBrowser gets focus, some keyboard and mouse events no longer fire in my app.
To reproduce: Create a new project, set XAML:
<Window x:Class="ProblemKeyboard.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">
<Grid>
<WebBrowser x:Name="browser" Height="177" HorizontalAlignment="Left" Margin="12,12,0,0" VerticalAlignment="Top" Width="479" />
</Grid>
</Window>
and let the codebehide override OnKeyDown() event.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
browser.Navigate("http://www.google.com");
//The above line causes browser to focus
//and as a consequence the OnKeyDown() handler
//doesn't get called again
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter) MessageBox.Show("Yey!");
base.OnKeyDown(e);
}
}
Okay, understandably the user might want to type his Google query. But at some point I want to get control back. To this end, I've devised a button. When you click this button I want keyboard control to come back to the WPF app. But no matter what the button does, I can't get OnKeyDown() to fire again.
My particular restrictions allow WebBrowser to be destroyed at this point. I tried clearing its parent container, tried calling Dispose() and the garbage collector. Tried Focus()ing on things that have that functionality. Nothing seems to get control back.
I'd rather avoid solutions which create new Window() or something to that effect.
EDIT
I've found that putting a TextBox and making it focus gets me back focus! However I have no textboxes in my window, and adding one just for giggles seems counter-intuitive at best.
EDIT 2
Current temporary solution puts an invisible (well, kinda, it's just 0 by 0, Visibility.Hidden doesn't work) TextBox - enables it, focuses it and disables it. Without disabling it first some keys are handled by TextBox instead of bubbling up to KeyDown().
Yes it is reproducible for Enter key only and the fix is to use OnKeyUp() for Enter Key....
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Enter) MessageBox.Show("Hi");
base.OnKeyUp(e);
}
I've been pounding away at this issue for a little while, and have only found part of the solution.
I'm trying to set up a TabControl so that I can in some cases prevent the user from changing the currently selected tab. When the user is prevented from changing the currently selected tab, then they are shown a dialog box.
I have already read the following documents:
WPF - reset ListBox scroll position when ItemsSource changes
http://wizardsofsmart.net/uncategorized/itemssourcechanged-event-using-attached-dependency-properties/
http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/
http://social.expression.microsoft.com/Forums/en-US/wpf/thread/f7b46018-1e97-4bbe-ada8-49b75dbc1da2/
I have implemented the solution indicated in the 3rd link (though all of the above create the same error seen below). And it works, but...
Things mess up thoroughly if the user does the following:
attempts to change the tab when such an action is disallowed. The MessageBox pops up with the error.
the user clicks "OK" and is returned to the original window.
the user tries again to change the tab. No MessageBox appears.
if the user minimizes the window, and then maximizes it again, then the MessageBox that was supposed to appear earlier appears.
the user clicks "OK" and is returned to the original window... but the tab has been changed to the one they selected before, even though they should not be able to change tabs.
This is obviously not ideal behavior. Why isn't the MessageBox appearing the second time, and why is the tab changing when it should be disallowed from doing so?
If I remove the MessageBox part, it works fine.
Here is the code for the TabControl.SelectionChanged event handler:
bool _isChanging = false;
private void tabControlForNavigation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isChanging && canChangeTabs.IsChecked.HasValue)
{
_isChanging = true;
bool canLeave = canChangeTabs.IsChecked.Value; //normally this would be replaced by a check in the ViewModel
if (!canLeave)
{
int prevIndex = tabControlForNavigation.Items.IndexOf(tabControlForNavigation.SelectedContent);
tabControlForNavigation.SelectedIndex = prevIndex;
MessageBox.Show("Can't change tabs!"); //if I comment out this line, everything works fine.
}
_isChanging = false;
}
}
I am using MVVM to implement this. The Window looks like this:
<Window x:Class="TestTabControlSwitching.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox x:Name="canChangeTabs"
Content="Can Change Tabs"
IsChecked="True" />
<TabControl x:Name="tabControlForNavigation"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem}"
SelectionChanged="tabControlForNavigation_SelectionChanged"
Margin="4"
HorizontalAlignment="Stretch">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
I'm omitting the rest of the code for sake of brevity- there is a pretty straight-forward ViewModel structure backing the window.
As you noticed, the problem is the MessageBox inside the event handler. The focus will change to the MessageBox and you can get all kind of undesired effects. I've had my own problems with this.
Here is a couple of SO question on the same subject
WPF: Does MessageBox Break PreviewMouseDown?
Wpf stop routing event when MessageBox appear?
If you must display a message to the user then an alternate approach might be to create a new Window which you style like a MessageBox and then call Show (not ShowDialog) on it inside the event handler.
I know this post is a bit old, but I have a very easy way to accomplish this:
Use the tab_Enter event and create a method that performs your check and displays a MessageBox to the user and then set myTabs.SelectedIndex to the prior index. A simple example:
private void someTab_Enter(object sender, EventArgs e)
{
if (myCondition)
{
MessageBox.Show("Sorry, myCondition will not let you move to this tab.");
myTabs.SelectedIndex = someOtherTabIndex;
}
}
This was a very detailed question. I had the same problem you had (i.e. the message box doesn't display on 2nd or 3rd selection changed until you minimize and maximize the window) and after much debugging and multiple google searches, stumbled on the below linked MSDN forum post.
[TabControl SelectionChanged Strange Behaviour?]
Please ignore the poorly formatted question and answer. But as mentioned in the answer, putting it inside a dispatcher and focussing the selected tab after setting the index resolved the issue for me.
You are missing an easy trick. Just make focusable=False for the Tab header.
<TabItem Header="MY TAB" Focusable="False">
You could bind this property to your view model.
<TabItem Header="MY TAB" Focusable="{Binding Bool_CanHasCheeseBurger}">