Is there Selected Tab Changed Event in the standard WPF Tab Control - wpf

In WPF, is there an event that can be used to determine when a TabControl's selected tab changes?
I have tried using TabControl.SelectionChanged but it is getting fired many times when a child's selection within a tab is changed.

I tied this in the handler to make it work:
void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.Source is TabControl)
{
//do work when tab is changed
}
}

If you set the x:Name property to each TabItem as:
<TabControl x:Name="MyTab" SelectionChanged="TabControl_SelectionChanged">
<TabItem x:Name="MyTabItem1" Header="One"/>
<TabItem x:Name="MyTabItem2" Header="2"/>
<TabItem x:Name="MyTabItem3" Header="Three"/>
</TabControl>
Then you can access to each TabItem at the event:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MyTabItem1.IsSelected)
// do your stuff
if (MyTabItem2.IsSelected)
// do your stuff
if (MyTabItem3.IsSelected)
// do your stuff
}

If you just want to have an event when a tab is selected, this is the correct way:
<TabControl>
<TabItem Selector.Selected="OnTabSelected" />
<TabItem Selector.Selected="OnTabSelected" />
<TabItem Selector.Selected="OnTabSelected" />
<!-- You can also catch the unselected event -->
<TabItem Selector.Unselected="OnTabUnSelected" />
</TabControl>
And in your code
private void OnTabSelected(object sender, RoutedEventArgs e)
{
var tab = sender as TabItem;
if (tab != null)
{
// this tab is selected!
}
}

You could still use that event. Just check that the sender argument is the control you actually care about and if so, run the event code.

If you're using the MVVM pattern then it is inconvenient (and breaks the pattern) to use the event handler. Instead, you can bind each individual TabItem's Selector.IsSelected property to a dependency property in your viewmodel and then handle the PropertyChanged event handler. That way you know exactly which tab was selected/deselected based on the PropertyName and you have a special handler for each tab.
Example: MainView.xaml
<TabControl>
<TabItem Header="My tab 1" Selector.IsSelected="{Binding IsMyTab1Selected}"> ... </TabItem>
<TabItem Header="My tab 2" Selector.IsSelected="{Binding IsMyTab2Selected}"> ... </TabItem>
</TabControl>
Example: MainViewModel.cs
public bool IsMyTab1Selected {
get { return (bool)GetValue(IsMyTab1SelectedProperty); }
set { SetValue(IsMyTab1SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab1SelectedProperty =
DependencyProperty.Register("IsMyTab1Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(true, new PropertyChangedCallback(MyPropertyChanged)));
public bool IsMyTab2Selected {
get { return (bool)GetValue(IsMyTab2SelectedProperty); }
set { SetValue(IsMyTab2SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab2SelectedProperty =
DependencyProperty.Register("IsMyTab2Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(false, new PropertyChangedCallback(MyPropertyChanged)));
private void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (e.Property.Name == "IsMyTab1Selected") {
// stuff to do
} else if (e.Property.Name == "IsMyTab2Selected") {
// stuff to do
}
}
If your MainViewModel is INotifyPropertyChanged rather than DependencyObject, then use this instead:
Example: MainViewModel.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel() {
PropertyChanged += handlePropertyChanged;
}
public bool IsMyTab1Selected {
get { return _IsMyTab1Selected ; }
set {
if (value != _IsMyTab1Selected ) {
_IsMyTab1Selected = value;
OnPropertyChanged("IsMyTab1Selected ");
}
}
}
private bool _IsMyTab1Selected = false;
public bool IsMyTab2Selected {
get { return _IsMyTab2Selected ; }
set {
if (value != _IsMyTab2Selected ) {
_IsMyTab2Selected = value;
OnPropertyChanged("IsMyTab2Selected ");
}
}
}
private bool _IsMyTab2Selected = false;
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "IsMyTab1Selected") {
// stuff to do
} else if (e.PropertyName == "IsMyTab2Selected") {
// stuff to do
}
}

The event generated is bubbling up until it is handled.
This xaml portion below triggers ui_Tab_Changed after ui_A_Changed when the item selected in the ListView changes, regardless of TabItem change in the TabControl.
<TabControl SelectionChanged="ui_Tab_Changed">
<TabItem>
<ListView SelectionChanged="ui_A_Changed" />
</TabItem>
<TabItem>
<ListView SelectionChanged="ui_B_Changed" />
</TabItem>
</TabControl>
We need to consume the event in ui_A_Changed (and ui_B_Changed, and so on):
private void ui_A_Changed(object sender, SelectionChangedEventArgs e) {
// do what you need to do
...
// then consume the event
e.Handled = true;
}

That is the correct event. Maybe it's not wired up correctly?
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="One"/>
<TabItem Header="2"/>
<TabItem Header="Three"/>
</TabControl>
in the codebehind....
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int i = 34;
}
if I set a breakpoint on the i = 34 line, it ONLY breaks when i change tabs, even when the tabs have child elements and one of them is selected.

This code seems to work:
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabItem selectedTab = e.AddedItems[0] as TabItem; // Gets selected tab
if (selectedTab.Name == "Tab1")
{
// Do work Tab1
}
else if (selectedTab.Name == "Tab2")
{
// Do work Tab2
}
}

If anyone use WPF Modern UI,they cant use OnTabSelected event.but they can use SelectedSourceChanged event.
like this
<mui:ModernTab Layout="Tab" SelectedSourceChanged="ModernTab_SelectedSourceChanged" Background="Blue" AllowDrop="True" Name="tabcontroller" >
C# code is
private void ModernTab_SelectedSourceChanged(object sender, SourceEventArgs e)
{
var links = ((ModernTab)sender).Links;
var link = this.tabcontroller.Links.FirstOrDefault(l => l.Source == e.Source);
if (link != null) {
var index = this.tabcontroller.Links.IndexOf(link);
MessageBox.Show(index.ToString());
}
}

Related

KeyBinding with Command Binding dont work with TextBox UpdateSourceTrigger LostFocus

I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.
is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?
some code to check out the problem
<Window>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
<Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
_data = new Viewmodel();
InitializeComponent();
this.DataContext = _data;
}
}
public class Viewmodel : INPCBase
{
private string _myText1;
private Lazy<DelegateCommand> _save;
public Viewmodel()
{
this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
}
private void SaveCommandExecute()
{
MessageBox.Show(MyText1);
}
public string MyText1
{
get { return _myText1; }
set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
}
public ICommand SaveCommand
{
get { return _save.Value; }
}
}
at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
{
var fe = Keyboard.FocusedElement as UIElement;
if (fe != null)
{
fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
I have the same problem and end up with attached property for TextBox.
public static bool GetCommitOnSave(DependencyObject obj)
{
return (bool)obj.GetValue(CommitOnSaveProperty);
}
public static void SetCommitOnSave(DependencyObject obj, bool value)
{
obj.SetValue(CommitOnSaveProperty, value);
}
public static readonly DependencyProperty CommitOnSaveProperty =
DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));
private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
if ((bool)e.NewValue)
{
if ((bool)e.NewValue)
{
textBox.KeyDown += TextBox_KeyDown;
}
else
{
textBox.KeyDown -= TextBox_KeyDown;
}
}
}
}
private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
}
Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />
Of course you can set attached property in style for all TextBoxes in a form.
I think I find the best solution for me. I mix solution #blindmeis and my previous one with using attached property.
I create command which update binding source of actual keyboard focused element:
public class CommitValueCommand : ICommand
{
private static CommitValueCommand _instance;
public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (Keyboard.FocusedElement is TextBox textBox)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
//for combobox etc.
else if (Keyboard.FocusedElement is Selector selector)
{
BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
}
}
}
In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().

Text changed event fired when control loaded, need to prevent on load?

In I have created a control that has a text box and a text changed event handler attached to it - this is in xaml.
The problem: when control is loaded the text changed event is fired, I do not want it to happen when the control is loaded only when I make actually make it change on the control by typing something.
What do you pros suggest I do? :)
All you have to do is check the textbox's IsLoaded property inside the event handler before handling it.
Attach Your EventHandler after the InitializeComponent Method in your constructor not in the Xaml.
i.e.
public MainWindow()
{
InitializeComponent();
textBox1.TextChanged+=new TextChangedEventHandler(textBox1_TextChanged);
}
I noticed that you are talking about an usercontrol, the only thing I can think of off the top of my head is to to create a property that can be used to inhibit the TextChanged Event until the Parent Form finishes loading. See if something like this works.
MainForm Xaml:
<my:UserControl1 setInhibit="True" HorizontalAlignment="Left" Margin="111,103,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="55" Width="149" setText="Hello" />
MainForm CS
private void Window_Loaded(object sender, RoutedEventArgs e)
{
userControl11.setInhibit = false;
}
UserControl:
public UserControl1()
{
InitializeComponent();
textBox1.TextChanged += new TextChangedEventHandler(textBox1_TextChanged);
}
public string setText
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
public bool setInhibit { get; set; }
void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
if (setInhibit) return;
// Do your work here
}
UserControl1.xaml:
<Grid>
<TextBox Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</Grid>
where TextChanged is the original event for TextBox
UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
_isFirstTime = true;
DataContext = this;
InitializeComponent();
}
public event TextChangedEventHandler TextBoxTextChanged;
bool _isFirstTime;
//MyText Dependency Property
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""));
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (TextBoxTextChanged != null)
if (!_isFirstTime)
{
TextBoxTextChanged(sender, e);
}
_isFirstTime = false;
}
}
where TextBox_TextChanged is the customized eventHandler for original TextChanged
and TextBoxTextChanged is more like a wrapper for the original TextChanged
Window.xaml:
<Grid>
<c:UserControl1 TextBoxTextChanged="TextBoxValueChanged"/>
</Grid>
as you see you can add an eventHandler to the event wrapper (TextBoxTextChanged)
Window.xaml.cs:
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
MessageBox.Show("asd");
}
finally TextBoxValueChanged won't be fired the first time Text is changed
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
if (Textbox1.IsFocused)
{
App.Current.Properties["TextChanged"] = "1"; // Set Flag
}
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (App.Current.Properties["TextChanged"] == "1")
{
// Do Your Wor Here
App.Current.Properties["TextChanged"] = "0"; // Clear Flag
}
}
On your XAML:
<TextBox xName="TextBox1" LostFocus="TextBoxLostFocus" TextChanged="TextBoxValueChanged"/>
(This is a very rudimentary, dirty, codebehind hack... checking the IsLoaded property as stated by Brent I found to be efficient)
Here since on textbox control creation it's not focused, the TextChanged event will fire but the flag "1" is NOT set...
Later when user leaves field after editing it, since it had focus the Flag is set... the LostFocus is fired, but only runnig code if textbox was changed.
I found a way of preventing this behavior across multiple inputs without having to create a unique bool for each input...
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsFocused)
return;
//The rest of your code here
}
So basically, if the text field doesn't have focus (like on initialization) it just returns. This also prevents it from firing if the data is changed elsewhere. :)
Alternatively, as mentioned by Brent, you can just look for "IsLoaded":
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsLoaded)
return;
//The rest of your code here
}

WPF ComboBox, whenever bound data changed, set SelectedIndex to 0?

I couldn't find the right event to achieve the functionality.
TargetUpdated event didn't work.
setting SelectedIndex to 0 on xaml would only affect the first load of data.
You can:
Set NotifyOnTargetUpdated on the binding
Add an event handler for Binding.TargetUpdated
In that event handler register for ItemsSource.CollectionChanged
In that event handler set the selected index to zero
The issue is most likely that you didn't set NotifyonTargetUpdated in the binding so the first event wasn't fired or that the collection was being updated but it was the same collection so the second event is necessary.
Here's a working example using a ListBox as the ItemsControl and a MessageBox as a proxy for doing whatever you want to do when the event fires.
Here is the markup:
<Grid>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Update Target" Click="ButtonUpdateTarget_Click"/>
<Button DockPanel.Dock="Top" Content="Update Item" Click="ButtonUpdateItem_Click"/>
<ListBox Name="listBox" Binding.TargetUpdated="ListBox_TargetUpdated" ItemsSource="{Binding Items, NotifyOnTargetUpdated=True}"/>
</DockPanel>
</Grid>
and here is the code-behind:
public class ViewModel : INotifyPropertyChanged
{
ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get { return items; }
set { items = value; OnPropertyChanged("Items"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
void SetDataContext()
{
DataContext = viewModel;
viewModel.Items = new ObservableCollection<string> { "abc", "def", "ghi" };
}
ViewModel viewModel = new ViewModel();
private void ButtonUpdateTarget_Click(object sender, RoutedEventArgs e)
{
viewModel.Items = new ObservableCollection<string> { "xyz", "pdq" };
}
private void ButtonUpdateItem_Click(object sender, RoutedEventArgs e)
{
viewModel.Items[0] = "xxx";
}
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
MessageBox.Show("Target Updated!");
(listBox.ItemsSource as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler(listBox_CollectionChanged);
}
void listBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MessageBox.Show("Item Updated!");
}
Have you tried the SourceUpdated event?
I faced same problem. To overcome this problem, I used the following steps:
create a TextBox
Set visibility of TextBox to Collapsed
Bind Text to ListBox.Items.Count
<TextBox x:Name="txtCount" TextChanged="TextBox_TextChanged" Text="{Binding ElementName=ListBox1, Path=Items.Count, Mode=OneWay}" Visibility="Collapsed" />
In the TextBox_TextChanged event, set SelectedIndex to 0
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
int count = 0;
if(int.TryParse(txtCount.Text,out count) && count>0)
ListBox1.SelectedIndex = 0;
}

Keeping keyboard focus on a single control while still beeing able to use a ListBox

Working on a TouchScreen application which also has a keyboard attached, I have the following problem:
The WPF window has a TextBox, which should receive ALL keyboard input. There are also Buttons and a ListBox, which are solely used by the TouchScreen(=Mouse).
A very simple example looks like this:
<Window x:Class="KeyboardFocusTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<StackPanel>
<TextBox Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewLostKeyboardFocus="TextBox_PreviewLostKeyboardFocus"/>
<Button Click="Button_Click">Add</Button>
<ListBox ItemsSource="{Binding Strings}" />
</StackPanel>
</Window>
To keep the TextBox always focused, I just do:
private void TextBox_PreviewLostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
So far so good - the problem now is, that I can't select items from the ListBox anymore. This only seems to work, if the ListBox has the keyboard focus. But if I loose the keyboard focus on the TextBox, I can't enter text anymore without clicking it first.
Any ideas, comments suggestions are welcome!
There might be a more elegant solution for this, but you could always handle the PreviewKeyDown event at the Window level, and pass focus to the TextBox if it doesn't already have it, instead of preventing it from losing focus in the first place. That way, the ListBox can use focus as is normal, but as soon as a key is pressed focus jumps right to the TextBox. In addition, you can filter out keys that you don't want to switch focus - the arrow keys come to mind, which could then be used to move up and down in the ListBox.
Adding an event handler like the following should do the trick:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!textBox.IsFocused)
{
textBox.Focus();
}
}
Based on Nicholas' suggestion (thx!), here's a markup extension, which is used like:
<TextBox Helpers:KeyboardFocusAttractor.IsAttracted="true" />
It seems to work, and ANTS didn't show any memory leaks. But when it comes to WPF and especially events and bindings, you never know, so use with care!
public static class KeyboardFocusAttractor
{
public static readonly DependencyProperty IsAttracted = DependencyProperty.RegisterAttached("IsAttracted",
typeof (bool), typeof (KeyboardFocusAttractor), new PropertyMetadata(false, OnIsAttracted));
private static void OnIsAttracted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var isAttracted = (bool) e.NewValue;
var controlWithInputFocus = d as Control;
if (controlWithInputFocus != null)
{
if (isAttracted)
{
new KeyboardInputFocusEventManager(controlWithInputFocus);
}
}
}
public static void SetIsAttracted(DependencyObject dp, bool value)
{
dp.SetValue(IsAttracted, value);
}
public static bool GetIsAttracted(DependencyObject dp)
{
return (bool) dp.GetValue(IsAttracted);
}
private class KeyboardInputFocusEventManager
{
private readonly Control _control;
private Window _window;
public KeyboardInputFocusEventManager(Control control)
{
_control = control;
_control.Loaded += ControlLoaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
_control.Unloaded += ControlUnloaded;
}
private void ControlLoaded(object sender, RoutedEventArgs e)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_control.Unloaded += ControlUnloaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
if (_control.IsVisible)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ControlUnloaded(object sender, RoutedEventArgs e)
{
_control.Unloaded -= ControlUnloaded;
_control.IsVisibleChanged -= ControlIsVisibleChanged;
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_window != null)
{
_window.PreviewKeyDown -= ParentWindowPreviewKeyDown;
}
if (_control.IsVisible)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ParentWindowPreviewKeyDown(object sender, KeyEventArgs e)
{
Keyboard.Focus(_control);
}
}
}

Interpret enter as tab WPF

I want to interpret Enter key as Tab key in whole my WPF application, that is, everywhere in my application when user press Enter I want to focus the next focusable control,except when button is focused. Is there any way to do that in application life circle? Can anyone give me an example?
Thanks a lot!
You can use my EnterKeyTraversal attached property code if you like. Add it to the top-level container on a WPF window and everything inside will treat enter as tab:
<StackPanel my:EnterKeyTraversal.IsEnabled="True">
...
</StackPanel>
Based on Richard Aguirre's answer, which is better than the selected answer for ease of use, imho, you can make this more generic by simply changing the Grid to a UIElement.
To change it in whole project you need to do this
In App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new KeyEventHandler(Grid_PreviewKeyDown));
base.OnStartup(e);
}
private void Grid_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var uie = e.OriginalSource as UIElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
uie.MoveFocus(
new TraversalRequest(
FocusNavigationDirection.Next));
}
}
Compile.
And done it. Now you can use enter like tab.
Note: This work for elements in the grid
I got around woodyiii's issue by adding a FrameworkElement.Tag (whose value is IgnoreEnterKeyTraversal) to certain elements (buttons, comboboxes, or anything I want to ignore the enter key traversal) in my XAML. I then looked for this tag & value in the attached property. Like so:
if (e.Key == Key.Enter)
{
if (ue.Tag != null && ue.Tag.ToString() == "IgnoreEnterKeyTraversal")
{
//ignore
}
else
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
woodyiii, There is a function in the UIElement called PredictFocus() which by its name know its function, then you can check if that element is enabled or not so as to move the focus to it or not...
Here is Matt Hamilton's code, if anyone is wondering since his site is down apparently:
public class EnterKeyTraversal
{
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
static void ue_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var ue = e.OriginalSource as FrameworkElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
private static void ue_Unloaded(object sender, RoutedEventArgs e)
{
var ue = sender as FrameworkElement;
if (ue == null) return;
ue.Unloaded -= ue_Unloaded;
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
typeof(EnterKeyTraversal), new UIPropertyMetadata(false, IsEnabledChanged));
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ue = d as FrameworkElement;
if (ue == null) return;
if ((bool)e.NewValue)
{
ue.Unloaded += ue_Unloaded;
ue.PreviewKeyDown += ue_PreviewKeyDown;
}
else
{
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
}
}
Another, a more on/off implementation approach would be to use behaviors:
public class TextBoxEnterFocusesNextBehavior :
Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
base.OnDetaching();
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args)
{
if (args.Key != Key.Enter) { return; }
args.Handled = true;
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
Usage example:
<UserControl xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Your.Namespace.To.Behaviors"
...>
<DockPanel>
<TextBox x:Name="TextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
<TextBox x:Name="TextBoxWithoutBehavior"
DockPanel.Dock="Top" />
<TextBox x:Name="AnotherTextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
</DockPanel>
</UserControl>
My solution:
public class MoveToNext : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (parameter is RoutedEventArgs routedEventArgs && routedEventArgs.OriginalSource is FrameworkElement element)
{
routedEventArgs.Handled = true;
element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
Usage:
<StackPanel>
<i:Interaction.Triggers>
<i:KeyTrigger Key="Return">
<util:MoveToNext/>
</i:KeyTrigger>
</i:Interaction.Triggers>
<!-- put your controls here -->
</StackPanel>
If you want the behavior to be attached to only one control instead of all controls within a layouter, simply add the <i:Interaction.Triggers block to that specific control.

Resources