Graeme_Grant
Вы можете сделать это в коде позади, однако может быть грязно, если вам придется делать это для нескольких элементов управления. Ответ заключается в том, что там, где вам нужно использовать код более чем один раз, использовать поведение, а не код позади, чтобы инкапсулировать код, чтобы избежать одного и того же кода в нескольких местах, уменьшить количество ошибок и упростить обслуживание. Затем в XAML свяжите элемент управления со свойствами ViewModel. Вот что будет делать это решение...
Во-первых, нам нужно добавить основной механизм привязки данных для уведомления представления и команду для связи от представления к ViewModel:
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool) DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> execute;
private readonly Predicate<T> canExecute;
#endregion
#region Constructors
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}
#endregion
#region ICommand Members
public bool CanExecute(object parameter)
=> canExecute == null || canExecute((T)parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
=> execute(parameter == null
? default(T)
: (T)Convert.ChangeType(parameter, typeof(T)));
#endregion
}
Теперь о поведении... Он присоединяется к элементу управления, на который нам нужно установить фокус, и привязывается к свойству в ViewModel, чтобы вызвать изменение фокуса...
public static class FocusHelper
{
public static bool GetIsFocused(DependencyObject ctrl)
=> (bool)ctrl.GetValue(IsFocusedProperty);
public static void SetIsFocused(DependencyObject ctrl, bool value)
=> ctrl.SetValue(IsFocusedProperty, value);
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusHelper),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var ctrl = (UIElement)d;
if ((bool)e.NewValue)
{
ctrl.Focus(); // Don't care about false values.
}
}
}
Способ, которым он будет работать, заключается в том, что при нажатии кнопки устанавливается свойство, уведомляющее о поведении для установки фокуса на присоединенный элемент управления.
Итак, вот ViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (IsInDesignMode)
{
// design time only...
}
else
{
// runtime only...
}
}
#region Relay Command
public RelayCommand<string> SelectCommand => new RelayCommand<string>(SetSelection);
private void SetSelection(string value)
{
SelectThis = value == "this";
SelectThat = value == "that";
}
#endregion
#region Properties
private bool selectThis;
public bool SelectThis
{
get => selectThis;
set => Set(ref selectThis, value);
}
private bool selectThat;
public bool SelectThat
{
get => selectThat;
set => Set(ref selectThat, value);
}
private string thisText = "ThisText";
public string ThisText
{
get => thisText;
set => Set(ref thisText, value);
}
private string thatText = "ThatText";
public string ThatText
{
get => thatText;
set => Set(ref thatText, value);
}
#endregion
}
Наконец, теперь мы можем подключить вид:
<Window x:Class="WpfSetFocus.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:l="clr-namespace:WpfSetFocus"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="10 5"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Grid.Row" Value="1"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Width" Value="200"/>
<Setter Property="Grid.Row" Value="2"/>
</Style>
</Grid.Resources>
<Button Content="Select This"
Command="{Binding SelectCommand}"
CommandParameter="this"/>
<TextBlock Text="{Binding SelectThis, StringFormat=Selected: {0}}"/>
<TextBox l:FocusHelper.IsFocused="{Binding SelectThis}"
Text="{Binding ThisText}"/>
<Button Content="Select That"
Grid.Column="1"
Command="{Binding SelectCommand}"
CommandParameter="that"/>
<TextBlock Text="{Binding SelectThat, StringFormat=Selected: {0}}"
Grid.Column="1"/>
<TextBox Grid.Column="1"
l:FocusHelper.IsFocused="{Binding SelectThat}"
Text="{Binding ThatText}"/>
<TextBlock Text="{Binding IsInDesignMode, StringFormat=Is in Design Mode: {0}}"
Grid.ColumnSpan="2"/>
</Grid>
</Window>
Наслаждайтесь! :)