MVVM Экранная клавиатура и ввод в разные TextBox
Есть окно-форма авторизации, 2 TextBox и кнопка Login, для этой формы нужно сделать экранную клавиатуру, которая будет вводить данные тот TextBox, на который нажали. Как это можно сделать на WPF, в рамках паттерна MVVM?
View:
<UserControl x:Class="PadVol2.Views.Pages.RegUserPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PadVol2.Views.Pages"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<StackPanel Orientation="Horizontal" Margin="50">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Номер пользователя:" HorizontalAlignment="Right" VerticalAlignment="Bottom" Foreground="White" Margin="0,0,5,5" Height="16" Width="118" />
<TextBox x:Name="login" Text="{Binding UID}" Width="100" HorizontalAlignment="Left" VerticalAlignment="Bottom" Grid.Column="1" Margin="5,0,0,5" Height="18" MaxLength="2"/>
<TextBlock Text="Пароль:" HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Row="1" Foreground="White" Margin="0,5,5,0" Height="16" Width="44" />
<PasswordBox Name="passwordBox" Width="100" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="1" Grid.Row="1" Margin="5,5,0,0" Height="18"/>
<Button Command="{Binding Command}" CommandParameter="{Binding ElementName=passwordBox}" Content="Войти" Grid.Column="1" Grid.Row="1" Width="75" Height="20" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="30,28,0,0" />
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="1" Height="40" Content="1" Margin="2" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="2" Height="40" Content="2" Margin="2" Grid.Column="1" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="3" Height="40" Content="3" Margin="2" Grid.Column="2" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="4" Height="40" Content="4" Margin="2" Grid.Row="1" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="5" Height="40" Content="5" Margin="2" Grid.Row="1" Grid.Column="1" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="6" Height="40" Content="6" Margin="2" Grid.Row="1" Grid.Column="2" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="7" Height="40" Content="7" Margin="2" Grid.Row="2" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="8" Height="40" Content="8" Margin="2" Grid.Row="2" Grid.Column="1" FontSize="20" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="9" Height="40" Content="9" Margin="2" Grid.Row="2" Grid.Column="2" FontSize="20" />
<Button Width="40" Command="{Binding DelLogin}" Height="40" Content="Удалить" Margin="2" Grid.Row="3" FontSize="8" />
<Button Width="40" Command="{Binding AddCharLogin}" CommandParameter="0" Height="40" Content="0" Margin="2" Grid.Row="3" Grid.Column="1" FontSize="20" />
<Button Width="40" Command="{Binding ClearLogin}" Height="40" Content="Очистить" Margin="2" Grid.Row="3" Grid.Column="2" FontSize="8" />
</Grid>
</StackPanel>
</Viewbox>
ViewModel:
class RegUserPageVM :ViewModelBase, IPageViewModel
{
public string UID { get; set; } = "";
public string Password { get; set; }
public ICommand Command { get; set; }
public ICommand AddCharLogin
{
get
{
return new DelegateCommand<object>(x =>
{
UID += x.ToString();
}, CanAddCharLogin);
}
}
private bool CanAddCharLogin(object param)
{
return this.UID.Length < 2;
}
public ICommand DelLogin
{
get
{
return new DelegateCommand(() =>
{
if (UID.Length > 0)
UID = UID.Substring(0, UID.Length - 1);
});
}
}
public ICommand ClearLogin
{
get
{
return new DelegateCommand(() =>
{
UID = "";
});
}
}
}
Ответы (1 шт):
Так как задача специфична, без код-бихайнда обойтись не удалось, но удалось реализовать то, что нужно. Я сделал в рамках обычного окна, но вы можете распределить логику по юзер-контролам, это не должно составить труда.
Я не использую внешние библиотеки и классы для MVVM реализаций, вместо этого использую 2 следующих вспомогательных класса, думаю, вы знаете, что они делают:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private const string _deleteText = "Удалить";
private const string _clearText = "Очистить";
private const int _loginLength = 6;
private const int _passwordLength = 4;
private string _uid = string.Empty;
private string _password = string.Empty;
private ICommand _keyPressCommand;
private ICommand _loginCommand;
public bool LoginFocused { get; set; }
public bool PasswordFocused { get; set; }
public string Uid
{
get => _uid;
set
{
_uid = value;
OnPropertyChanged();
}
}
public string Password
{
get => _password;
set
{
_password = value;
OnPropertyChanged();
OnPropertyChanged(nameof(PasswordView));
}
}
public string PasswordView => new string('\u25cf', _password?.Length ?? 0);
public ICommand KeyPressCommand => _keyPressCommand ?? (_keyPressCommand = new RelayCommand(parameter =>
{
if (parameter is int number)
{
if (LoginFocused)
Uid += number;
else if (PasswordFocused)
Password += number;
}
if (parameter is string command)
{
switch (command)
{
case _deleteText:
if (LoginFocused)
Uid = Uid.Remove(Uid.Length - 1);
else if (PasswordFocused)
Password = Password.Remove(Password.Length - 1);
break;
case _clearText:
if (LoginFocused)
Uid = string.Empty;
else if (PasswordFocused)
Password = string.Empty;
break;
}
}
}, parameter =>
(parameter is int && ((LoginFocused && Uid?.Length < _loginLength) || (PasswordFocused && Password?.Length < _passwordLength))) ||
(parameter is string && ((LoginFocused && Uid?.Length > 0) || (PasswordFocused && Password?.Length > 0)))
));
public ICommand LoginCommand => _loginCommand ?? (_loginCommand = new RelayCommand(parameter =>
{
MessageBox.Show(string.Format("Uid: {0}{1}Password: {2}", Uid, Environment.NewLine, Password));
}));
public IEnumerable<object> PadKeys => GetKeys();
private IEnumerable<object> GetKeys()
{
for (int i = 1; i < 10; i++)
yield return i;
yield return _deleteText;
yield return 0;
yield return _clearText;
}
public MainViewModel()
{
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private readonly MainViewModel _vm;
public MainWindow()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
private void LoginBox_FocusChanged(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBox)
_vm.LoginFocused = textBox.IsFocused;
}
private void PasswordBox_FocusChanged(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBox)
_vm.PasswordFocused = textBox.IsFocused;
}
}
Разметка адаптивная, можно менять размер окна, размер кнопок подстроится под любое разрешение. Настройте себе марджины и паддинги по вкусу.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Width="600" Height="300" FontSize="20" FocusManager.FocusedElement="{Binding ElementName=LoginTextBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="Номер пользователя:" Margin="0,5,0,0"/>
<TextBox x:Name="LoginTextBox" Margin="0,5" Text="{Binding Uid, Mode=OneWay}" IsReadOnly="True" GotFocus="LoginBox_FocusChanged" LostFocus="LoginBox_FocusChanged"/>
<TextBlock Text="Пароль:" Margin="0,5,0,0" />
<TextBox Margin="0,5" Text="{Binding PasswordView, Mode=OneWay}" IsReadOnly="True" GotFocus="PasswordBox_FocusChanged" LostFocus="PasswordBox_FocusChanged"/>
<Button Content="Войти" Padding="20,5" HorizontalAlignment="Right" Command="{Binding LoginCommand}"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding PadKeys}" Grid.Column="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5" Command="{Binding DataContext.KeyPressCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Focusable="False" >
<Button.Content>
<Viewbox Margin="5">
<TextBlock Text="{Binding}"/>
</Viewbox>
</Button.Content>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Window>
