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);
}
Related
In my WPF application, I want to handle user F5 strokes as refresh. In order to archieve that, I decided to utilize the NavigationCommands.Refresh command.
Inside the UI, I utilize the DataGridControl from Extended WPF Toolkit. The Problem: whenever the focus is within the data grid, the refresh command handler is not triggered.
This can be demonstrated with a very small sample:
<Window x:Class="WpfTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xd="http://schemas.xceed.com/wpf/xaml/datagrid">
<Window.CommandBindings>
<CommandBinding Command="NavigationCommands.Refresh" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"/>
</Window.CommandBindings>
<StackPanel>
<TextBox Text="Click me to get the focus out of DataGridControl"/>
<xd:DataGridControl/>
</StackPanel>
</Window>
Nothing fancy going on in code behind, I just use it to place breakpoints in the handler:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
e.Handled = true;
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
}
Reproduce:
Start Application, press F5 - handler is executed
Click into the DataGridControl area, press F5 - handler is not executed
Click into the textbox, press F5 - handler is executed
So the question is, how can I ensure that my refresh handler is executed when the user presses F5 while focus is within the DataGridControl?
Ok, I finally stumbled upon the solution.
https://xceed.com/forums/topic/what-is-the-function-key-F5-in-datagrid-for/
Setting the DataGridControl.IsRefreshCommandEnabled property to False stops the datagridcontrol from consuming the F5 key for its own internal logic. Then the handler is called as expected.
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.
I am having a simple WPF Ribbon Window using RibbonControlsLibrary.
In this Window I am having a RibbonComboBox which contains an Button Element. My intent is to close RibbonComboBox' dropdown once the Button is clicked. I do not want manipulate the IsOpen property of the RibbonComboBox, since the Button should be reused for different purposes. My idea was to handle the MouseDown event of the button and then set e.Handled to false
The XAML looks like this
<ribbon:RibbonWindow x:Class="WpfRibbonApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
xmlns:my="clr-namespace:WpfRibbonApplication3"
Title="MainWindow"
x:Name="RibbonWindow"
Width="640" Height="480">
<ribbon:Ribbon>
<ribbon:RibbonTab Header="Home">
<ribbon:RibbonGroup Header="Group1">
<ribbon:RibbonComboBox>
<ribbon:RibbonGallery>
<ribbon:RibbonGalleryCategory>
<ribbon:RibbonGalleryItem>
<my:CustomButton Content="Test" />
</ribbon:RibbonGalleryItem>
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonComboBox>
</ribbon:RibbonGroup>
</ribbon:RibbonTab>
</ribbon:Ribbon>
</ribbon:RibbonWindow>
The code behind looks as follows:
public CustomButton()
{
// Insert code required on object creation below this point.
AddHandler(MouseDownEvent,(RoutedEventHandler)Button_MouseDown,true);
}
private void Button_MouseDown(object sender, RoutedEventArgs e)
{
Console.WriteLine(string.Format("MouseDownEvent, e.Handled = {0}", e.Handled));
e.Handled = false;
}
The event gets fired but the Ribbon dropdown closes only when pressing the button the second time. Interestingly, the second time I press the button the MouseDown event handler does not get invoked. Can someone give me some hints on how to achieve closing of the ribbon on the first click in a clean manner?
You can give your RibbonComboBox a name, let's say x:Name="comboBox", then in the code behind in the mouse down event handler, you can simply do: comboBox.IsDropDownOpen = false;
I want to perform a particular action when user cancels my dialog by clicking the close button (red X button)
and not when the form is closing because of some other operation. How i can determine whether the
private void Window_Closing(object sender, CancelEventArgs e)
event is raised by the button ?
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="60" Width="284" WindowStartupLocation="CenterScreen"
BorderBrush="#FFCCCCCC"
BorderThickness="2"
Background="#FFE0E0E0"
WindowStyle="SingleBorderWindow"
ShowInTaskbar="False" ResizeMode="NoResize" Closing="Window_Closing">
public MainWindow()
{
InitializeComponent();
this.Closing+=new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
}
private void MainWindow_Closing(object sender, EventArgs e)
{
MessageBox.Show("salman");
}
My question is what are the other ways of closing this window? My understanding is the sender is always going to be the Window.
I would do the following - for all buttons or user based close - set a public property on the Window (something like bool ClosedByUser) and set it to "true" and then call Close().
For other cases (such as closing by clicking "X"), the property is by default set to false. In the closing event handler, use the property to make the decision.
Please note: There are other questions and answers similar to this on StackOverflow.
I have keyboard shortcuts declared in my xaml using KeyBindings.
I would like to ignore repetitions due to key holding in few of them.
I have found only solutions using events and checking "IsRepetition", which doesnt really fit in my declaration of the keybindings.
Of course I could do it in the Command definition itself and measure a time difference between 2 last executes, but this gives me no way to differentiate multiple presses and 1 key holding.
What would be the best way to execute only on the first press and ignore the rest if the key is hold?
You are trying to change a behavior of the button. Better to use code for that.
The easiest way is to attach a preview event to the window like that:
<Window
...
PreviewKeyDown="HandlePreviewKeyDown">
Then in code handle it like that:
private void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat)
{
e.Handled = true;
}
}
Sadly this would disable any repeat behavior, even in a textbox hosted by the form. This is an interesting question. If I find a more elegant way of doing this, I will add to the answer.
EDIT:
OK there are two ways to define Key Binding.
<Window x:Class="WpfApplication1.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.InputBindings>
<KeyBinding x:Name="altD" Gesture="Alt+D" Command="{Binding ClickCommand}"/>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="_Click" Command="{Binding ClickCommand}" />
<TextBox Grid.Row="1"/>
</Grid>
</Window>
The above button will generate a click because you implicitely requested the Alt-C gesture via the underscore: _Click content. Then the window has an explicit keybinding to Alt+D.
This code behind should now work for both cases and should not interfere with regular repeat:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.IsRepeat)
{
if (((KeyGesture)altD.Gesture).Matches(this, e))
{
e.Handled = true;
}
else if (e.Key == Key.System)
{
string sysKey = e.SystemKey.ToString();
//We only care about a single character here: _{character}
if (sysKey.Length == 1 && AccessKeyManager.IsKeyRegistered(null, sysKey))
{
e.Handled = true;
}
}
}
}
I would say if you create a very simple state machine of sorts that would take action on the KeyBinding on a KeyDown event and would ignore all other input until a KeyUp event is fired to give the KeyBinding a "one-shot" behavior.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.keyup.aspx
Use the keyUp method instead of KeyDown.