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.
Related
I am trying to use RoutedCommands in my UserControls, following the example in this article:
https://joshsmithonwpf.wordpress.com/2008/03/18/understanding-routed-commands/
I defined the RoutedCommand and CommandBindings in the UserControl instead of in the article's example. I am trying to use it in my MainWindow, so that when the Button is clicked, the Command in the UserControl is executed. However, the Button is disabled and the Foo_CanExecute() method is never executed.
<UserControl x:Class="RoutedCommandTest.ViewControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:RoutedCommandTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.CommandBindings>
<CommandBinding
Command="{x:Static local:ViewControl.Foo}"
PreviewCanExecute="Foo_CanExecute"
PreviewExecuted="Foo_Executed"
/>
</UserControl.CommandBindings>
<Grid>
</Grid>
</UserControl>
Here is the code for ViewControl.xaml.cs:
public static readonly RoutedCommand Foo = new RoutedCommand();
void Foo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
void Foo_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("The Window is Fooing...");
}
public ViewControl()
{
InitializeComponent();
}
And here is the code for MainWindow.xaml:
<Window x:Class="RoutedCommandTest.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:RoutedCommandTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:ViewControl/>
<Button Content="Foo" Margin="0 5" Command="{x:Static local:ViewControl.Foo}"/>
</Grid>
</Window>
I would like to know how to fix the issue so that the Button is enabled and the Foo_CanExecute() method is executed when the Button is clicked.
Your command is in a usercontrol, whilst the button is in mainwindow.
Which presumably contains your usercontrol.
Like bubbling and routing events ( which are used to drive them ).
Executed looks for the command bubbling UP the visual tree to the binding.
PreviewExecuted looks for the command tunnelling DOWN the visual tree to the binding.
Since your button is in the parent of the usercontrol I'm not sure whether either bubbling or tunnelling will work.
But tunnelling would be PreviewExecuted And PreviewCanExecute.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandbinding.previewexecuted?view=netframework-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandbinding.previewcanexecute?view=netframework-4.8
Routedcommands can be pretty tricky to get right.
One thing you sometimes have to do is to bind commandtarget to tell it where to go look.
eg:
<Grid>
<local:UserControl1 x:Name="UC1" Height="60" Width="100"/>
<Button Content="Foo" TextElement.FontSize="30" Command="{x:Static local:UserControl1.Foo}"
CommandTarget="{Binding ElementName=UC1}"
/>
</Grid>
Works for me.
I have rarely found them useful - this is one of the aspects makes them way less useful than you might at first imagine.
EDIT:
It's perhaps worth mentioning the other thing makes these unattractive compared to a regular icommand. You need to either use a static which means it's only suitable for very generic commands OR you need event handlers which will be in code behind.
On the other hand.
If you're writing something has to work generically with whatever has focus. Like say a text editor with multiple textboxes and you're doing text manipulation. A routed command might be suitable. I have never encountered such a requirement in apps I've worked on though.
I encountered this behavior with WinRT's ListView.
LayoutUpdated event does fire less often if no event handler is attached to the inner ScrollViewer's LayoutUpdated event.
I marked the critical line near the bottom with // === Critical line ===.
Removing the leading slashes will lead to lv_LayoutUpdated being called more often.
This has implications since the event is fired far too seldom for it to be of any use to me (in the commented-out case)
The problem is, that I cannot set the scroll position before the ScrollViewer's mutation has come to an end. Routing through the ScrollViewer's event seems silly, though.
Is this a bug or is it explainable with bubbling strategies or sth.?
EDIT: BTW, this page is not shown in the app's root frame, but in a frame of a different page if that makes any difference.
EDIT2: I ended up disovering why the firing timing was useless (was doing the data binding async, which screwed with the standard event timings). The behavior persists, though - subscribing to ScrollViewer's event leads to more publishing by the ListView's event. Under what additional circumstances this ends up happening, I do not know. I am using Caliburn.Micro - so maybe their data source wiring does something to the control.
E.g. one issue with CM is, that it swallows the first LayoutUpdated event of the view (if bound via Message:Attach atleast) - although this might only affect the view model.
Anyway, I'll leave this question be for others to disover.
This setup in the code-behind:
public FileListView()
{
this.InitializeComponent();
var lv = (ListView)FindName("Files");
lv.Loaded += lv_Loaded;
lv.LayoutUpdated += lv_LayoutUpdated;
}
void lv_LayoutUpdated(object sender, object e)
{
var m = (FileListViewModel)DataContext;
m.Offset = 100;
}
void lv_Loaded(object sender, RoutedEventArgs e)
{
var lv = (ListView)sender;
var sv = lv.GetFirstDescendantOfType<ScrollViewer>();
if (sv == null)
{
return;
}
//sv.LayoutUpdated += sv_LayoutUpdated; // === Critical line ===
}
void sv_LayoutUpdated(object sender, object e)
{
// No code here. Just the subscription suffices
}
The XAML:
<Page
x:Class="Namespace1.FileListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Poolar.Mobile.PsProject.WinRT.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid HorizontalAlignment="Left" Height="768" VerticalAlignment="Top" Width="1366">
<Grid.RowDefinitions>
<RowDefinition Height="61*"/>
<RowDefinition Height="707*"/>
</Grid.RowDefinitions>
<ListView x:Name="Files" HorizontalAlignment="Left" Height="446" VerticalAlignment="Top" Width="1366"
Margin="10,10,-10,0" Grid.Row="1" />
<Button x:Name="GoBack" x:Uid="ButtonGoBack" Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
</Grid>
</Page>
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}">
I use standard Cut, Copy and Paste commands (which is part of ApplicationCommands class). Is it possible to redefine CanExecute method?
Here is my code:
XAML:
<Window.CommandBindings>
<CommandBinding Command="Copy"
CanExecute="CopyCanExecute" Executed="CopyExecuted"/>
</Window.CommandBindings>
<StackPanel>
<TextBox Name="txt"></TextBox>
<Button Command="Copy" CommandTarget="{Binding ElementName=txt}">copy</Button>
</StackPanel>
Code-behind:
private void CopyCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = false;
}
private void CopyExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Copy Executed");
}
The button still behave like its command is standard Copy command.
You do this via a CommandBinding. The local CommandBinding can specify a CanExecuteHandler.
For details and a working example, see this blog post.
The copy command will not work when the focus is on a textbox where the commands have already been handled, but it will work on elements like CheckBox etc.
In the CanExecute handler you might need to add `e.Handled = true; also, so that it doesnt go and execute the standard Copy.CanExecute()
You can set the commandbinding to the textbox directly instead of to the window.