从视图模型(C#)设置WPF中的文本框焦点
我在我的视图中有一个TextBox
和一个Button
。
现在我正在检查按钮单击条件,如果条件结果是false,显示消息给用户,然后我必须将光标设置到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); }
上面的代码在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}"/>
让我回答你的问题三个部分。
-
我想知道什么是“cs.txtCompanyID”在你的例子? 它是一个TextBox控件? 如果是的话,那么你的方式是错误的。 一般来说,在ViewModel中引用UI并不是一个好主意。 你可以问“为什么?” 但这是另一个问题发布在Stackoverflow :)。
-
追踪焦点问题的最佳方法是调试.Net源代码。 没有开玩笑 它为我节省了很多时间。 要启用.net源代码调试,请参阅Shawn Bruke的博客。
-
最后,我用来从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}" />
希望这可以帮助 :)。 如果没有参考答案#2。
干杯。
我知道这个问题到现在已经回答了一千多遍了,但是我对安华嘉的贡献做了一些编辑,我觉得会帮助那些和我有类似问题的人。
首先,我改变了上面的附加属性,如下所示:
public static class FocusExtension { public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged)); 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); } }
我添加可见性引用的原因是选项卡。 显然,如果您在最初可见的选项卡之外的任何其他选项卡上使用了附加的属性,附加的属性将不起作用,直到您手动聚焦该控件。
另一个障碍是创建一个更优雅的方式,当失去焦点时将基础属性重置为false。 这就是丢失焦点事件的地方,确保将xaml中的绑定设置为双向模式。
<TextBox Text="{Binding Description}" FocusExtension.IsFocused="{Binding IsFocused, Mode=TwoWay}"/>
如果有更好的方法来处理可见性问题,请让我知道。
我认为最好的方法是保持MVVM原理清洁,所以基本上你必须使用MVVM Light提供的Messenger类,下面是如何使用它:
在你的viewmodel(exampleViewModel.cs)中:写下面的内容
Messenger.Default.Send<string>("focus", "DoFocus");
现在在你的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(); }
该方法很好,代码少,维护MVVM标准
这些都没有为我工作,但为了别人的利益,这是我最终编写的基于这里已经提供的一些代码。
用法如下:
<TextBox ... h:FocusBehavior.IsFocused="True"/>
执行情况如下:
/// <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 }
这是一个古老的线程,但似乎没有解决与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); } }
话虽如此,但对于可以在代码隐藏的一行中完成的事情来说,这仍然很复杂,并且CoerceValue并不是真正用于这种方式的,所以也许是隐藏的方法。
问题是,一旦IsUserNameFocused设置为true,它永远不会是错误的。 这可以通过处理FrameworkElement的GotFocus和LostFocus来解决。
我在源代码格式有问题,所以这里是一个链接
在我的情况下,直到我改变方法OnIsFocusedPropertyChanged FocusExtension不工作。 原来的一个只在调试中断时才能停止进程。 在运行时,这个过程太快,没有任何事情发生。 有了这个小小的修改和我们的朋友任务的帮助,这两个方案工作正常。
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); } }
Anvakas的辉煌代码是针对Windows桌面应用程序的。 如果您像我一样,需要Windows Store应用程序的相同解决方案,则此代码可能非常方便:
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 PropertyMetadata(false, OnIsFocusedPropertyChanged)); private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue) { var uie = d as Windows.UI.Xaml.Controls.Control; if( uie != null ) { uie.Focus(FocusState.Programmatic); } } } }
对于那些试图使用上面的Anvaka解决方案的人,我只是在第一次工作时遇到了绑定问题,因为lostFocus不会将属性更新为false。 您可以手动将属性设置为false,然后每次都为true,但更好的解决方案可能是在您的属性中执行如下操作:
bool _isFocused = false; public bool IsFocused { get { return _isFocused ; } set { _isFocused = false; _isFocused = value; base.OnPropertyChanged("IsFocused "); } }
这样,你只需要把它设置为true,它会得到重点。
我使用WPF / Caliburn Micro发现“dfaivre”已经在这里提出了一个通用且可行的解决方案: http ://caliburnmicro.codeplex.com/discussions/222892
我发现Crucial对IsVisible问题的解决方案非常有用。 它并没有完全解决我的问题,但是一些额外的代码遵循IsEnabled模式的相同模式。
我添加了IsFocusedChanged方法:
if (!fe.IsEnabled) { fe.IsEnabledChanged += fe_IsEnabledChanged; }
这里是处理程序:
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(); } }
对于Silverlight:
using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; namespace MyProject.Behaviors { public class FocusBehavior : Behavior<Control> { protected override void OnAttached() { this.AssociatedObject.Loaded += AssociatedObject_Loaded; base.OnAttached(); } private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { this.AssociatedObject.Loaded -= AssociatedObject_Loaded; if (this.HasInitialFocus || this.IsFocused) { this.GotFocus(); } } private void GotFocus() { this.AssociatedObject.Focus(); if (this.IsSelectAll) { if (this.AssociatedObject is TextBox) { (this.AssociatedObject as TextBox).SelectAll(); } else if (this.AssociatedObject is PasswordBox) { (this.AssociatedObject as PasswordBox).SelectAll(); } else if (this.AssociatedObject is RichTextBox) { (this.AssociatedObject as RichTextBox).SelectAll(); } } } public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.Register( "IsFocused", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) => { if ((bool)e.NewValue) { ((FocusBehavior)d).GotFocus(); } })); public bool IsFocused { get { return (bool)GetValue(IsFocusedProperty); } set { SetValue(IsFocusedProperty, value); } } public static readonly DependencyProperty HasInitialFocusProperty = DependencyProperty.Register( "HasInitialFocus", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, null)); public bool HasInitialFocus { get { return (bool)GetValue(HasInitialFocusProperty); } set { SetValue(HasInitialFocusProperty, value); } } public static readonly DependencyProperty IsSelectAllProperty = DependencyProperty.Register( "IsSelectAll", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, null)); public bool IsSelectAll { get { return (bool)GetValue(IsSelectAllProperty); } set { SetValue(IsSelectAllProperty, value); } } } }
LoginViewModel.cs:
public class LoginModel : ViewModelBase { .... private bool _EmailFocus = false; public bool EmailFocus { get { return _EmailFocus; } set { if (value) { _EmailFocus = false; RaisePropertyChanged("EmailFocus"); } _EmailFocus = value; RaisePropertyChanged("EmailFocus"); } } ... }
Login.xaml:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:beh="clr-namespace:MyProject.Behaviors" <TextBox Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <i:Interaction.Behaviors> <beh:FocusBehavior IsFocused="{Binding EmailFocus}" IsSelectAll="True"/> </i:Interaction.Behaviors> </TextBox>
要么
<TextBox Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <i:Interaction.Behaviors> <beh:FocusBehavior HasInitialFocus="True" IsSelectAll="True"/> </i:Interaction.Behaviors> </TextBox>
设置焦点应该在代码中完成:
EmailFocus = true;
请记住,这个插件是html页面的一部分,因此页面中的其他控件可能会成为焦点
if (!Application.Current.IsRunningOutOfBrowser) { System.Windows.Browser.HtmlPage.Plugin.Focus(); }
您可以使用ViewCommand设计模式。 它描述了一个MVVM设计模式的方法,用命令控制ViewModel的View。
我已经根据King A.Majid建议使用MVVM Light Messenger类来实现它。 ViewCommandManager类处理在连接的视图中调用命令。 这基本上是常规命令的另一个方向,对于ViewModel需要在View中执行某些操作的情况。 它使用反射像数据绑定命令和WeakReferences来避免内存泄漏。
http://dev.unclassified.de/source/viewcommand (也在CodeProject上发布)
我已经找到解决方案,按照以下编辑代码。 没有必要先设置Binding属性False然后是True。
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) { if (d != null && d is Control) { var _Control = d as Control; if ((bool)e.NewValue) { // To set false value to get focus on control. if we don't set value to False then we have to set all binding //property to first False then True to set focus on control. OnLostFocus(_Control, null); _Control.Focus(); // Don't care about false values. } } } private static void OnLostFocus(object sender, RoutedEventArgs e) { if (sender != null && sender is Control) { (sender as Control).SetValue(IsFocusedProperty, false); } } }
没有人似乎已经包括了通过绑定变量更容易更新属性的最后一步。 这是我想出来的。 让我知道是否有更好的方法来做到这一点。
XAML
<TextBox x:Name="txtLabel" Text="{Binding Label}" local:FocusExtension.IsFocused="{Binding txtLabel_IsFocused, Mode=TwoWay}" /> <Button x:Name="butEdit" Content="Edit" Height="40" IsEnabled="{Binding butEdit_IsEnabled}" Command="{Binding cmdCapsuleEdit.Command}" />
视图模型
public class LoginModel : ViewModelBase { public string txtLabel_IsFocused { get; set; } public string butEdit_IsEnabled { get; set; } public void SetProperty(string PropertyName, string value) { System.Reflection.PropertyInfo propertyInfo = this.GetType().GetProperty(PropertyName); propertyInfo.SetValue(this, Convert.ChangeType(value, propertyInfo.PropertyType), null); OnPropertyChanged(PropertyName); } private void Example_function(){ SetProperty("butEdit_IsEnabled", "False"); SetProperty("txtLabel_IsFocused", "True"); } }
public class DummyViewModel : ViewModelBase { private bool isfocused= false; public bool IsFocused { get { return isfocused; } set { isfocused= value; OnPropertyChanged("IsFocused"); } } }
System.Windows.Forms.Application.DoEvents(); Keyboard.Focus(tbxLastName);