Xamarin.Forms+Prism(MVVM)中自定义输入框如何正确设置焦点
SacTextBox继承自ContentView,属于布局容器类型,本身不承接输入焦点。直接调用实例的Focus()方法,焦点请求只会作用在ContentView根容器上,不会传递给内部封装的名为txtBox的Entry输入控件,这就是焦点设置不生效的核心原因。
1. 基础改造:让自定义控件支持焦点转发
打开SacTextBox的后台代码文件SacTextBox.xaml.cs,重写基类的Focus、Unfocus方法,把焦点请求直接转发给内部的Entry控件:
public new bool Focus() { // 把焦点请求透传给内部实际的输入框 return txtBox.Focus(); } public new void Unfocus() { txtBox.Unfocus(); }
改完之后,你之前在页面代码后置写的Descripcion.Focus();就可以正常生效,适合直接在按钮点击事件(写在xaml.cs中)里调用的场景。
2. MVVM+Prism适配(推荐,符合架构规范)
Prism架构下不建议在ViewModel中直接持有控件实例,通过可绑定属性实现焦点控制更符合MVVM分层要求:
第一步:给SacTextBox添加焦点可绑定属性
在SacTextBox.xaml.cs中追加以下代码,实现支持绑定的焦点状态属性,同时同步Entry原生的焦点事件,避免状态不一致:
public static readonly BindableProperty IsFocusedProperty = BindableProperty.Create( propertyName: nameof(IsFocused), returnType: typeof(bool), declaringType: typeof(SacTextBox), defaultValue: false, propertyChanged: OnIsFocusedPropertyChanged); public new bool IsFocused { get => (bool)GetValue(IsFocusedProperty); set => SetValue(IsFocusedProperty, value); } private static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldValue, object newValue) { var control = bindable as SacTextBox; if (control == null) return; bool targetFocusState = (bool)newValue; if (targetFocusState) control.txtBox.Focus(); else control.txtBox.Unfocus(); // 同步Entry原生焦点事件,保证双向绑定状态一致 control.txtBox.Focused -= control.SyncFocusStateFromEntry; control.txtBox.Unfocused -= control.SyncFocusStateFromEntry; control.txtBox.Focused += control.SyncFocusStateFromEntry; control.txtBox.Unfocused += control.SyncFocusStateFromEntry; } private void SyncFocusStateFromEntry(object sender, FocusEventArgs e) { IsFocused = e.IsFocused; }
第二步:ViewModel中添加焦点控制逻辑
在对应页面的ViewModel(当前场景下是FrmArticulosGeneralViewModel)中,添加焦点状态属性和触发命令,Prism的BindableBase已经自带属性变更通知,直接用SetProperty即可:
private bool _isDescripcionFocused; public bool IsDescripcionFocused { get => _isDescripcionFocused; set => SetProperty(ref _isDescripcionFocused, value); } private DelegateCommand _triggerFocusDescripcionCommand; public DelegateCommand TriggerFocusDescripcionCommand => _triggerFocusDescripcionCommand ??= new DelegateCommand(ExecuteTriggerFocusDescripcion); private void ExecuteTriggerFocusDescripcion() { // 先重置为false,避免重复设置true时不触发属性变更回调 IsDescripcionFocused = false; IsDescripcionFocused = true; }
第三步:页面XAML中完成绑定
修改页面XAML,给SacTextBox添加IsFocused属性绑定,给触发焦点的按钮绑定对应命令:
<sofadControls:SacTextBox x:Name="Descripcion" Grid.Column="1" Placeholder="Descripción" SacAlineacion="Start" SacValidacion="AlfaNumerico" IsFocused="{Binding IsDescripcionFocused}" Text="{Binding Source={RelativeSource AncestorType={x:Type dialogos:FrmArticulosGeneralViewModel}}, Path=DetalleActual.Descripcion}"/> <!-- 触发焦点的按钮示例 --> <Button Text="定位到描述输入框" Command="{Binding TriggerFocusDescripcionCommand}"/>
补充注意:如果需要在页面初次加载时自动定位焦点,不要在构造函数、OnAppearing的同步逻辑中直接触发,要等页面控件渲染完成后再执行。可以借助Xamarin Community Toolkit的EventToCommandBehavior绑定页面Loaded事件触发命令,避免控件未加载完成导致焦点设置失效。
内容的提问来源于stack exchange,提问作者Angel

