我为 Entry 控件创建了一个自定义行为,该行为允许 Entry 控件的集合提供一个单位数字代码输入字段,在输入值时将光标移动到下一个字段(向右),并移动到上一个字段删除值时的字段(左侧)。这大多有效。实际行为中仍然存在一些错误需要解决。我现在试图解决的问题是,作为 ViewModel 中支持字段的字符串数组没有得到设置调用,这意味着我无法引发属性更改通知。
下面列出了自定义行为代码..
public class AutoFocusBehavior : Behavior<Entry>
{
private string previousText = string.Empty;
public static readonly BindableProperty EntryTextChangedProperty =
BindableProperty.Create(nameof(EntryTextChanged),
typeof(EventHandler<TextChangedEventArgs>),
typeof(AutoFocusBehavior),
null);
public event EventHandler<TextChangedEventArgs> EntryTextChanged;
protected override void OnAttachedTo(Entry entry)
{
base.OnAttachedTo(entry);
entry.Focused += OnEntryFocused;
entry.TextChanged += OnEntryTextChanged;
}
protected override void OnDetachingFrom(Entry entry)
{
base.OnDetachingFrom(entry);
entry.Focused -= OnEntryFocused;
entry.TextChanged -= OnEntryTextChanged;
}
private void OnEntryFocused(object sender, FocusEventArgs e)
{
if (sender is Entry entry)
{
// Store the previous text if it's not null or empty
if (!string.IsNullOrEmpty(entry.Text))
{
previousText = entry.Text;
}
// Select all text when the Entry is focused
entry.SelectionLength = entry.Text?.Length ?? 0;
}
}
private void OnEntryTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is Entry entry)
{
if (e.NewTextValue.Length == 0 && e.OldTextValue.Length == 1)
{
// If the user deletes a character, move focus to the previous entry
if (!MoveFocusToPreviousEntry(entry))
{
// If it's the first entry, do not move focus
entry.Text = string.Empty;
}
}
else if (e.NewTextValue.Length == 1)
{
// If the text length is 1, move focus to the next entry and clear the current entry
entry.Unfocus();
MoveFocusToNextEntry(entry);
}
EntryTextChanged?.Invoke(sender, e);
}
}
private void MoveFocusToNextEntry(Entry currentEntry)
{
// Find the parent stack layout containing all the Entry elements
if (currentEntry.Parent is StackLayout stackLayout)
{
// Get the index of the current Entry
int currentIndex = stackLayout.Children.IndexOf(currentEntry);
// Move focus to the next Entry if it exists
if (currentIndex < stackLayout.Children.Count - 1)
{
Entry nextEntry = (Entry)stackLayout.Children[currentIndex + 1];
nextEntry.Focus();
}
}
}
private bool MoveFocusToPreviousEntry(Entry currentEntry)
{
// Find the parent stack layout containing all the Entry elements
if (currentEntry.Parent is StackLayout stackLayout)
{
// Get the index of the current Entry
int currentIndex = stackLayout.Children.IndexOf(currentEntry);
// If it's the first Entry and the text is empty, do not move focus
if (currentIndex == 0 && string.IsNullOrEmpty(currentEntry.Text))
{
return false;
}
// Move focus to the previous Entry if it exists
if (currentIndex > 0)
{
Entry previousEntry = (Entry)stackLayout.Children[currentIndex - 1];
previousEntry.Focus();
return true;
}
}
return false;
}
}
此代码绑定到的 XAML 是
<StackLayout x:Name="EntryStack" Orientation="Horizontal" HorizontalOptions="CenterAndExpand" Spacing="1" Grid.Row="1" Margin="0, 10">
<!-- Code Entry Boxes -->
<Entry WidthRequest="40" Text="{Binding CodeDigits[0], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[1], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[2], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[3], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[4], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[5], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[6], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
<Entry WidthRequest="40" Text="{Binding CodeDigits[7], Mode=TwoWay}" Keyboard="Numeric" MaxLength="1" HorizontalTextAlignment="Center">
<Entry.Behaviors>
<b:AutoFocusBehavior EntryTextChanged="{Binding EntryTextChangedCommand}" />
</Entry.Behaviors>
</Entry>
</StackLayout>
视图模型绑定到的属性是...
public string[] CodeDigits
{
get { return _codeDigits; }
set
{
if (_codeDigits != value)
{
_codeDigits = value;
RaisePropertyChanged(nameof(CodeDigits));
RaisePropertyChanged(nameof(IsVerifyButtonEnabled));
}
}
}
我尝试了许多不同的方法来触发 setter,包括为行为添加 BindableProperty 以及从行为自己的 OnEntryTextChanged 处理程序手动触发 EntryTextChanged 事件。我在这里遗漏了一些基本的东西。感谢任何帮助/指导:-)
您的 setter 不会被调用,因为它属于
CodeDigits
属性,而不是其中包含的字符串。
您可以尝试使用
ObservableCollection
而不是 string[]
,但我不确定它会改变什么。
我看到两种方法。
如果您知道需要多少个条目,假设 8 个,您可以使用 8 个
string
属性并为每个属性调用 RaisePropertyChanged。
否则,您必须创建一个更复杂的对象来实现
INotifyPropertyChanged
并具有 string
属性,然后将其中的 8 个对象存储在数组中。