C# 从视图模型将焦点设置在 WPF 中的 TextBox 上

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1356045/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:34:18  来源:igfitidea点击:

Set focus on TextBox in WPF from view model

c#wpfxamlmvvmtextbox

提问by priyanka.sarkar

I have a TextBoxand a Buttonin my view.

我有一个TextBox和一个Button在我看来。

Now I am checking a condition upon button click and if the condition turns out to be false, displaying the message to the user, and then I have to set the cursor to the TextBoxcontrol.

现在我正在检查按钮单击时的条件,如果条件为假,则向用户显示消息,然后我必须将光标设置到TextBox控件上。

if (companyref == null)
{
    var cs = new Lipper.Nelson.AdminClient.Main.Views.ContactPanels.CompanyAssociation(); 

    MessageBox.Show("Company does not exist.", "Error", MessageBoxButton.OK,
                    MessageBoxImage.Exclamation);

    cs.txtCompanyID.Focusable = true;

    System.Windows.Input.Keyboard.Focus(cs.txtCompanyID);
}

The above code is in the ViewModel.

上面的代码在ViewModel中。

The CompanyAssociationis the view name.

CompanyAssociation是视图名称。

But the cursor is not getting set in the TextBox.

但是光标没有在TextBox.

The xaml is:

xaml 是:

<igEditors:XamTextEditor Name="txtCompanyID" 
                         KeyDown="xamTextEditorAllowOnlyNumeric_KeyDown"
                         ValueChanged="txtCompanyID_ValueChanged"
                         Text="{Binding Company.CompanyId,
                                        Mode=TwoWay,
                                        UpdateSourceTrigger=PropertyChanged}"
                         Width="{Binding ActualWidth, ElementName=border}"
                         Grid.Column="1" Grid.Row="0"
                         VerticalAlignment="Top"
                         HorizontalAlignment="Stretch"
                         Margin="0,5,0,0"
                         IsEnabled="{Binding Path=IsEditable}"/>

<Button Template="{StaticResource buttonTemp1}"
        Command="{Binding ContactCommand}"
        CommandParameter="searchCompany"
        Content="Search"
        Width="80"
        Grid.Row="0" Grid.Column="2"
        VerticalAlignment="Top"
        Margin="0"
        HorizontalAlignment="Left"
        IsEnabled="{Binding Path=IsEditable}"/>

采纳答案by Anvaka

Let me answer to your question in three parts.

我分三部分回答你的问题。

  1. I'm wondering what is "cs.txtCompanyID" in your example? Is it a TextBox control? If yes, then you are on a wrong way. Generally speaking it's not a good idea to have any reference to UI in your ViewModel. You can ask "Why?" but this is another question to post on Stackoverflow :).

  2. The best way to track down issues with Focus is... debugging .Net source code. No kidding. It saved me a lot of time many times. To enable .net source code debugging refer to Shawn Bruke'sblog.

  3. Finally, general approach that I use to set focus from ViewModel is Attached Properties. I wrote very simple attached property, which can be set on any UIElement. And it can be bound to ViewModel's property "IsFocused" for example. Here it is:

    public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsFocusedProperty);
        }
    
        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }
    
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof (bool), typeof (FocusExtension),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
    
        private static void OnIsFocusedPropertyChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            var uie = (UIElement) d;
            if ((bool) e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }
    }
    

    Now in your View (in XAML) you can bind this property to your ViewModel:

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />
    
  1. 我想知道你的例子中的“cs.txtCompanyID”是什么?它是一个文本框控件吗?如果是,那么你走错了路。一般来说,在 ViewModel 中引用 UI 并不是一个好主意。你可以问“为什么?” 但这是在 Stackoverflow 上发布的另一个问题:)。

  2. 跟踪 Focus 问题的最佳方法是...调试 .Net 源代码。不开玩笑。它多次为我节省了很多时间。要启用 .net 源代码调试,请参阅Shawn Bruke 的博客。

  3. 最后,我用来从 ViewModel 设置焦点的一般方法是附加属性。我写了非常简单的附加属性,可以在任何 UIElement 上设置。例如,它可以绑定到 ViewModel 的属性“IsFocused”。这里是:

    public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsFocusedProperty);
        }
    
        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }
    
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof (bool), typeof (FocusExtension),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
    
        private static void OnIsFocusedPropertyChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            var uie = (UIElement) d;
            if ((bool) e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }
    }
    

    现在在您的视图中(在 XAML 中),您可以将此属性绑定到您的 ViewModel:

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />
    

Hope this helps :). If it doesn't refer to the answer #2.

希望这可以帮助 :)。如果它没有参考答案#2。

Cheers.

干杯。

回答by KitWest

System.Windows.Forms.Application.DoEvents();
Keyboard.Focus(tbxLastName);

回答by Shawn

The problem is that once the IsUserNameFocused is set to true, it will never be false. This solves it by handling the GotFocus and LostFocus for the FrameworkElement.

问题是一旦 IsUserNameFocused 设置为 true,它就永远不会为 false。这通过处理 FrameworkElement 的 GotFocus 和 LostFocus 来解决它。

I was having trouble with the source code formatting so here is a link

我在源代码格式方面遇到了问题,所以这里是一个链接

回答by Leo Vildosola

None of these worked for me exactly, but for the benefit of others, this is what I ended up writing based on some of the code already provided here.

这些都不完全适合我,但为了其他人的利益,这就是我最终根据此处提供的一些代码编写的内容。

Usage would be as follows:

用法如下:

<TextBox ... h:FocusBehavior.IsFocused="True"/>

And the implementation would be as follows:

实现如下:

/// <summary>
/// Behavior allowing to put focus on element from the view model in a MVVM implementation.
/// </summary>
public static class FocusBehavior
{
    #region Dependency Properties
    /// <summary>
    /// <c>IsFocused</c> dependency property.
    /// </summary>
    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?),
            typeof(FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
    /// <summary>
    /// Gets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Value of the <c>IsFocused</c> property or <c>null</c> if not set.</returns>
    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        return (bool?)element.GetValue(IsFocusedProperty);
    }
    /// <summary>
    /// Sets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(IsFocusedProperty, value);
    }
    #endregion Dependency Properties

    #region Event Handlers
    /// <summary>
    /// Determines whether the value of the dependency property <c>IsFocused</c> has change.
    /// </summary>
    /// <param name="d">The dependency object.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = d as FrameworkElement;
        if (fe != null && e.OldValue == null && e.NewValue != null && (bool)e.NewValue)
        {
            // Attach to the Loaded event to set the focus there. If we do it here it will
            // be overridden by the view rendering the framework element.
            fe.Loaded += FrameworkElementLoaded;
        }
    }
    /// <summary>
    /// Sets the focus when the framework element is loaded and ready to receive input.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = sender as FrameworkElement;
        if (fe != null)
        {
            // Remove the event handler registration.
            fe.Loaded -= FrameworkElementLoaded;
            // Set the focus to the given framework element.
            fe.Focus();
            // Determine if it is a text box like element.
            var tb = fe as TextBoxBase;
            if (tb != null)
            {
                // Select all text to be ready for replacement.
                tb.SelectAll();
            }
        }
    }
    #endregion Event Handlers
}

回答by Kyeotic

For those trying to use Anvaka's solution above, I was having issues with the binding only working the first time, as lostfocus would not update the property to false. You can manually set the property to false and then true every time, but a better solution could be to do something like this in your property:

对于那些尝试使用上述 Anvaka 解决方案的人,我在第一次绑定时遇到了问题,因为 lostfocus 不会将属性更新为 false。您可以手动将属性设置为 false 然后每次都设置为 true,但更好的解决方案可能是在您的属性中执行以下操作:

bool _isFocused = false;
    public bool IsFocused 
    {
        get { return _isFocused ; }
        set
        {
            _isFocused = false;
            _isFocused = value;
            base.OnPropertyChanged("IsFocused ");
        }
    }

This way you only ever need to set it to true, and it will get focus.

这样你只需要将它设置为 true,它就会得到焦点。

回答by Zamotic

I know this question has been answered a thousand times over by now, but I made some edits to Anvaka's contribution that I think will help others that had similar issues that I had.

我知道这个问题现在已经回答了一千次了,但我对 Anvaka 的贡献做了一些编辑,我认为这会帮助其他有类似问题的人。

Firstly, I changed the above Attached Property like so:

首先,我像这样更改了上面的附加属性:

public static class FocusExtension
{
    public static readonly DependencyProperty IsFocusedProperty = 
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged){BindsTwoWayByDefault = true});

    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        return (bool?)element.GetValue(IsFocusedProperty);
    }

    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(IsFocusedProperty, value);
    }

    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)d;

        if (e.OldValue == null)
        {
            fe.GotFocus += FrameworkElement_GotFocus;
            fe.LostFocus += FrameworkElement_LostFocus;
        }

        if (!fe.IsVisible)
        {
            fe.IsVisibleChanged += new DependencyPropertyChangedEventHandler(fe_IsVisibleChanged);
        }

        if ((bool)e.NewValue)
        {
            fe.Focus();
        }
    }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)sender;
        if (fe.IsVisible && (bool)((FrameworkElement)sender).GetValue(IsFocusedProperty))
        {
            fe.IsVisibleChanged -= fe_IsVisibleChanged;
            fe.Focus();
        }
    }

    private static void FrameworkElement_GotFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, true);
    }

    private static void FrameworkElement_LostFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, false);
    }
}

My reason for adding the visibility references were tabs. Apparently if you used the attached property on any other tab outside of the initially visible tab, the attached property didn't work until you manually focused the control.

我添加可见性引用的原因是选项卡。显然,如果您在初始可见选项卡之外的任何其他选项卡上使用附加属性,则附加属性在您手动聚焦控件之前不起作用。

The other obstacle was creating a more elegant way of resetting the underlying property to false when it lost focus. That's where the lost focus events came in.

另一个障碍是创建一种更优雅的方式来在失去焦点时将底层属性重置为 false。这就是失去焦点事件的来源。

<TextBox            
    Text="{Binding Description}"
    FocusExtension.IsFocused="{Binding IsFocused}"/>

If there's a better way to handle the visibility issue, please let me know.

如果有更好的方法来处理可见性问题,请告诉我。

Note: Thanks to Apfelkuacha for the suggestion of putting the BindsTwoWayByDefault in the DependencyProperty. I had done that long ago in my own code, but never updated this post. The Mode=TwoWay is no longer necessary in the WPF code due to this change.

注意:感谢 Apfelkuacha 建议将 BindsTwoWayByDefault 放在 DependencyProperty 中。我很久以前在我自己的代码中这样做过,但从未更新过这篇文章。由于此更改,WPF 代码中不再需要 Mode=TwoWay。

回答by Wayne Maurer

I found Crucial's solution to the IsVisible problem very useful. It didn't completely solve my problem, but some extra code following the same pattern for the IsEnabled pattern did.

我发现Crucial对 IsVisible 问题的解决方案非常有用。它并没有完全解决我的问题,但是遵循 IsEnabled 模式的相同模式的一些额外代码做到了。

To the IsFocusedChanged method I added:

对于我添加的 IsFocusedChanged 方法:

    if (!fe.IsEnabled)
    {
        fe.IsEnabledChanged += fe_IsEnabledChanged;
    }

And here's the handler:

这是处理程序:

private static void fe_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    var fe = (FrameworkElement)sender;
    if (fe.IsEnabled && (bool)((FrameworkElement)sender).GetValue(IsFocusedProperty))
    {
        fe.IsEnabledChanged -= fe_IsEnabledChanged;
        fe.Focus();
    }
}

回答by Adam

I think the best way is to keep the MVVM principle clean, so basically you must use the Messenger Class provided with the MVVM Light and here is how to use it:

我认为最好的方法是保持 MVVM 原理干净,所以基本上你必须使用 MVVM Light 提供的 Messenger 类,这里是如何使用它:

in your viewmodel(exampleViewModel.cs):write the following

在您的视图模型(exampleViewModel.cs)中:编写以下内容

 Messenger.Default.Send<string>("focus", "DoFocus");

now in your View.cs(not the XAML the view.xaml.cs) write the following in the constructor

现在在您的 View.cs(不是 XAML view.xaml.cs)中,在构造函数中写入以下内容

 public MyView()
        {
            InitializeComponent();

            Messenger.Default.Register<string>(this, "DoFocus", doFocus);
        }
        public void doFocus(string msg)
        {
            if (msg == "focus")
                this.txtcode.Focus();
        }

that method owrks just fine and with less code and maintaining MVVM standards

该方法运行良好,代码更少,并且维护 MVVM 标准

回答by Vincent Rithner

In my case, the FocusExtension didn't work until I change the method OnIsFocusedPropertyChanged. The original one was working only in debug when a break point stopped the process. At runtime, the process is too quick and nothing happend. With this little modification and the help of our friend Task, this is working fine in both scenarios.

就我而言,在我更改方法 OnIsFocusedPropertyChanged 之前,FocusExtension 不起作用。当断点停止进程时,原始的仅在调试中工作。在运行时,这个过程太快了,什么也没发生。通过这个小小的修改和我们朋友 Task 的帮助,这在两种情况下都可以正常工作。

private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var uie = (UIElement)d;
  if ((bool)e.NewValue)
  {
    var action = new Action(() => uie.Dispatcher.BeginInvoke((Action)(() => uie.Focus())));
    Task.Factory.StartNew(action);
  }
}

回答by kpp

I use WPF / Caliburn Micro a found that "dfaivre" has made a general and workable solution here: http://caliburnmicro.codeplex.com/discussions/222892

我使用 WPF / Caliburn Micro 发现“dfaivre”在这里提出了一个通用且可行的解决方案:http://caliburnmicro.codeplex.com/discussions/222892