从视图模型(C#)设置焦点在WPF中的文本框

pri*_*kar 124 c# wpf xaml textbox mvvm

在我看来,我有一个TextBox和一个Button.

现在我在点击按钮时检查条件,如果条件结果为假,向用户显示消息,然后我必须将光标设置为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);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在ViewModel中.

CompanyAssociation是视图名称.

但光标没有设置在TextBox.

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}"/>
Run Code Online (Sandbox Code Playgroud)

Anv*_*aka 253

让我分三部分回答你的问题.

  1. 我想知道你的例子中的"cs.txtCompanyID"是什么?它是TextBox控件吗?如果是,那么你走错了路.一般来说,在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.
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />
    
    Run Code Online (Sandbox Code Playgroud)

希望这可以帮助 :).如果它没有提到答案#2.

干杯.

  • 你还应该从你的'OnIsFocusedPropertyChanged`事件中调用`Keyboard.Focus(uie);`,如果你想让你的控件接收键盘焦点以及逻辑焦点 (17认同)
  • 很酷的主意.我需要将IsUserNameFocused设置为true,然后再将其设置为false以使其正常工作,这是正确的吗? (5认同)
  • 这应该如何使用?如果我将我的属性设置为true,则控件将被聚焦.但是当我回到这个观点时,它将永远集中.从OnIsFocusedPropertyChanged重置它不会改变它.从ViewModel设置后直接重置它不再聚焦任何东西.它不起作用.那些70名赞助人究竟做了什么? (5认同)
  • 我还将回调更改为:`... if((bool)e.NewValue && uie.Dispatcher!= null){uie.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(Action)(()=> uie.Focus ())); //调用行为更好,例如,你有一些附加到UIE的'GotFocus'的附加处理程序.uie.SetValue(IsFocusedProperty,false); //如果可能的话重置绑定值,以允许再次设置...`有时我甚至必须在ViewModel中将'IsFocused'重置为false,如果我想多次设置焦点.但是它有效,其他一些方法失败了. (4认同)
  • 我在一个对话框中设置文本框的焦点,我在WPF应用程序中显示对话框,将对话框显示为UserControl.在尝试了许多不同的方法之后,我设法最终使聚焦工作.你上面的代码工作后,我调整它使用Dispatcher.CurrentDispatcher.BeginInvoke做焦点的呼叫:Dispatcher.CurrentDispatcher.BeginInvoke((行动)(()=> {uie.Focus(); //不要在乎假值})); (2认同)
  • 你可以将`obj.SetValue(IsFocusedProperty,value);`更改为`obj.SetValue(IsFocusedProperty,false);`而不必再次设置false和true. (2认同)
  • 设置焦点并且另一个控件获得焦点后,再次设置焦点将不起作用,因为IsFocused仍然是真的.需要强制它为假,然后才是真的.`public bool IsFocused {get {return _isFocused; } set {if(_isFocused == value){_ isFocused = false; OnPropertyChanged(); } _isFocused = value; OnPropertyChanged(); } (2认同)

Zam*_*tic 68

我知道这个问题现在已被回答了一千次,但我对Anvaka的贡献进行了一些编辑,我认为这将有助于其他有类似问题的人.

首先,我改变了上面的附属物:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

<TextBox            
    Text="{Binding Description}"
    FocusExtension.IsFocused="{Binding IsFocused}"/>
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这对我很有用,除了我需要在GotFocus/LostFocus中添加"if(e.Source == e.OriginalSource)"检查,否则在我的UserControl上使用stackoverflows(字面意思),它会将焦点重定向到内部零件.我删除了Visible检查,接受了它就像.Focus()方法一样的事实.如果.Focus()不起作用,绑定应该不起作用 - 这对我的场景来说没问题. (9认同)

Ada*_*dam 30

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

在您的viewmodel(exampleViewModel.cs)中:编写以下内容

 Messenger.Default.Send<string>("focus", "DoFocus");
Run Code Online (Sandbox Code Playgroud)

现在在您的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();
        }
Run Code Online (Sandbox Code Playgroud)

这种方法很好,代码较少,维护MVVM标准

  • 厄尔尼诺:你究竟在哪里得到这个想法,你的视图中应该没有任何代码隐藏?任何与UI相关的东西都应该在视图的代码隐藏中.设置UI元素的焦点应该*绝对*在视图的代码隐藏中.让viewmodel弄清楚何时发送消息; 让视图弄清楚如何处理消息.*那就是MV-VM所做的:分离数据模型,业务逻辑和UI的关注点. (31认同)
  • 好吧,如果你想保持MVVM原理干净,那么你首先不会在代码中编写代码.我相信所附属性方法更清晰.它也不会在视图模型中引入很多魔术字符串. (8认同)

小智 18

这些都不能完全适用于我,但为了其他人的利益,这是我最终根据这里已经提供的一些代码编写的.

用法如下:

<TextBox ... h:FocusBehavior.IsFocused="True"/>
Run Code Online (Sandbox Code Playgroud)

实施如下:

/// <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
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*h N 11

这是一个旧线程,但似乎没有解决代码解决Anavanka接受的答案的问题:如果您将viewmodel中的属性设置为false,或者如果您将属性设置为是的,用户手动点击其他内容,然后再次将其设置为true.在这些情况下,我无法让Zamotic的解决方案可靠地工作.

将上面的一些讨论结合在一起,给出了下面的代码,我认为这些问题可以解决这些问题:

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, null, OnCoerceValue));

    private static object OnCoerceValue(DependencyObject d, object baseValue)
    {
        if ((bool)baseValue)
            ((UIElement)d).Focus();
        else if (((UIElement) d).IsFocused)
            Keyboard.ClearFocus();
        return ((bool)baseValue);
    }
}
Run Code Online (Sandbox Code Playgroud)

话虽如此,对于可以在代码隐藏中的一行中完成的事情来说,这仍然很复杂,并且CoerceValue并不是真的意味着以这种方式使用,所以也许代码隐藏是要走的路.

  • 这始终有效,而接受的答案则不然。谢谢! (2认同)

小智 5

就我而言,在我更改 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);
  }
}
Run Code Online (Sandbox Code Playgroud)