如何在执行搜索按钮async命令时从viewmodel设置焦点于WPF控件 [英] How to set focus on WPF control from viewmodel when search button async command executed

查看:78
本文介绍了如何在执行搜索按钮async命令时从viewmodel设置焦点于WPF控件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的代码后面我有一个方法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 属性 = Horizo​​ntalAlignment = 中心 / >
< Setter Property = VerticalAlignment Value = 中心 / >
< Setter 属性 = 填充 = 10 5 / >
< / Style >
< 样式 TargetType = TextBlock >
< Setter 属性 = Horizo​​ntalAlignment = 中心 / >
< Setter 属性 = VerticalAlignment = 中心 / >
< Setter 属性 = Grid.Row = 1 / >
< /样式 >
< 样式 TargetType = TextBox >
< Setter 属性 = Horizo​​ntalAlignment = 中心 / >
< 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}} / >
< 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}\"

Text=\"{Binding ThatText}\"/>

<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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆