如何在执行搜索按钮async命令时从viewmodel设置焦点于WPF控件 [英] How to set focus on WPF control from viewmodel when search button async command executed
问题描述
在我的代码后面我有一个方法OnFocusRequested,当执行异步搜索按钮命令时,我的ViewModel使用接口
调用。
问题是当我从代码中调试每一步时,它工作正常。这段代码不能实时工作。
我相信这是由于来自视图模型的那个按钮命令的异步调用
但是不知道如何解决这个并解决这个问题。
有什么建议吗?
我尝试了什么:
private void OnFocusRequested( object sender,FocusRequestedEventArgs e)
{
switch (e.PropertyName)
{
case StatementDate :
Application.Current.Dispatcher.BeginInvoke(
new Action(()= >
{
dateStatement.Focus();
Keyboard.Focus(dateStatement);
}),
DispatcherPriority.ContextIdle,
null
);
break ;
}
}
这是我的代码隐藏文件方法,其中我设置焦点我尝试过或不带调度程序但结果是相同的。
你可以在代码后面执行它但是如果必须为多个控件执行它会变得混乱。答案是,在需要多次使用代码的地方,使用行为而不是代码来封装代码以避免在多个位置使用相同的代码,减少错误并简化可维护性。然后在XAML中,将控件绑定到ViewModel属性。这就是这个解决方案将要做的...
首先,我们需要添加一个核心DataBinding机制来通知视图和一个Command从View通信到ViewModel:
public abstract class ObservableBase:INotifyPropertyChanged
{
public void 设置< TValue>( ref TValue字段,TValue newValue,[CallerMemberName] string propertyName = )
{
if (!EqualityComparer< TValue> .Default.Equals(field, default (TValue))&& field.Equals(newValue))< span class =code-keyword> return ;
field = newValue;
PropertyChanged?.Invoke( this , new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract 类 ViewModelBase:ObservableBase
{
public bool IsInDesignMode
= > ( bool )DesignerProperties.IsInDesignModeProperty
.GetMetadata(< span class =code-keyword> typeof (DependencyObject))
.DefaultValue;
}
public class RelayCommand< T> :ICommand
{
#region字段
私人 readonly 行动< T>执行;
private readonly 谓词< T> canExecute;
#endregion
#region构造函数
public RelayCommand(Action< T> execute,Predicate< T> canExecute = null )
{
this .execute = execute ?? throw new ArgumentNullException( 执行跨度>);
this .canExecute = canExecute;
}
#endregion
#region ICommand成员
public bool CanExecute( object 参数)
= > canExecute == null || canExecute((T)参数);
public event EventHandler CanExecuteChanged
{
< span class =code-keyword> add = > CommandManager.RequerySuggested + = value ;
删除 = > CommandManager.RequerySuggested - = 值跨度>;
}
public void 执行( object 参数)
= > 执行(参数== null
?默认(T)
:(T)Convert.ChangeType(参数, typeof运算跨度>(T)));
#endregion
}
现在为行为...它附加到我们需要设置焦点并绑定到ViewModel上的属性以触发焦点更改的控件...
< span class =code-keyword> 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(); // 不关心错误值。
}
}
}
它的工作方式是按下按钮时,设置属性以通知行为将焦点设置为附加控件。 />
所以这里是ViewModel:
public class MainViewModel:ViewModelBase
{
public MainViewModel()
{
if (IsInDesignMode)
{
// 仅限设计时间...
}
其他
{
// 仅限运行时...
}
}
#region Relay Command
public RelayCommand< string> SelectCommand = > new RelayCommand< string>(SetSelection);
private void SetSelection( string value )
{
SelectThis = value == < span class =code-string> this;
SelectThat = value == 跨度>;
}
#endregion
#region属性
私有 bool selectThis;
public bool SelectThis
{
获取 = > selectThis;
set = > 设置( ref selectThis, value );
}
private bool selectThat;
public bool SelectThat
{
获取 = > selectThat;
set = > 设置( ref selectThat, value );
}
private string thisText = ThisText;
public string ThisText
{
获取 = > thisText;
set = > 设置( ref thisText, value );
}
private string thatText = ThatText;
public string ThatText
{
get = > thatText;
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 < span class =code-attribute>
< span class =code-attribute> mc:可忽略 = d
标题 = MainWindow 高度 = 450 宽度 = 800 >
< Window.DataContext >
< l:MainViewModel / >
< / Window.DataContext >
< 网格 >
< Grid.ColumnDefinitions >
< ColumnDefinition / >
< ColumnDefinition / >
< / Grid.ColumnDefinitions >
< Grid.RowDefinitions >
< ; RowDefinition / >
&升t; RowDefinition / >
< RowDefinition / >
< / Grid.RowDefinitions >
< Grid.Resources >
< 样式 TargetType = 按钮 >
< Setter 属性 = HorizontalAlignment 值 = 中心 / >
< Setter Property = VerticalAlignment Value = 中心 / >
< Setter 属性 = 填充 值 = 10 5 / >
< / Style >
< 样式 TargetType = TextBlock >
< Setter 属性 = HorizontalAlignment 值 = 中心 / >
< Setter 属性 = VerticalAlignment 值 = 中心 / >
< Setter 属性 = Grid.Row 值 = 1 / >
< /样式 >
< 样式 TargetType = TextBox >
< Setter 属性 = HorizontalAlignment值 = 中心 / >
< Setter 属性 = VerticalAlignment 值 = 中心 / >
< < span class =code-leadattribute> Setter Property = 宽度 值 = 200 / >
< Setter Property = Grid.Row 值 = 2 < span class =code-keyword> / >
< / Style >
< / Grid.Resources >
< 按钮 内容 = 选择此
命令 = {Binding SelectCommand}
CommandParameter = this / < span class =code-keyword>>
<TextBlock Text = {Binding SelectThis,StringFormat = Selected:{0}} / > Text=\"{Binding ThatText}\"/>
< TextBox l:FocusHelper.IsFocused = {Binding SelectThis}
< span class =code-attribute> 文字 = {Binding ThisText} / >
< 按钮 内容 = 选择
< span class =code-attribute> 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}\"
<TextBlock Text=\"{Binding IsInDesignMode, StringFormat=Is in Design Mode: {0}}\"
Grid.ColumnSpan=\"2\"/>
</Grid>
</Window>
Enjoy! :)
In my code behind i have a method OnFocusRequested which is called using Interface
from my ViewModel when async search button command execute.
the problem is that it is working fine when i just debug through each step from code. this code is not work in real time.
I believe this is due to async call of that button command from view model
but dont know tp figure out this and fix this.
any suggestion ?
What I have tried:
private void OnFocusRequested(object sender, FocusRequestedEventArgs e)
{
switch (e.PropertyName)
{
case "StatementDate":
Application.Current.Dispatcher.BeginInvoke(
new Action(() =>
{
dateStatement.Focus();
Keyboard.Focus(dateStatement);
}),
DispatcherPriority.ContextIdle,
null
);
break;
}
}
this is my code behind file method in which i am setting the focus i have tried with or without dispatcher but result is same.
You can do it in code behind however can get messy if you have to do it for multiple controls. The answer is, where you need to use the code in more than one time, to use a behavior rather than code behind to encapsulate the code to avoid the same code in multiple places, reduces errors, and simplified maintainability. Then in XAML, bind the control to ViewModel properties. That is what this solution will do...
First, we need to add a core DataBinding mechanism for notifying the view and a Command to communicate from the View to the 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 }
Now for the behavior... It attaches to the control that we need to set focus to and binds to a property on the ViewModel to trigger the focus change...
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. } } }
The way that it will work is when a button is pressed, a property is set to notify the behavior to set the focus to the attached control.
So here is the 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 }
Lastly, we can now wire up the View:
<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>
Enjoy! :)
这篇关于如何在执行搜索按钮async命令时从viewmodel设置焦点于WPF控件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!