有几个关于错误验证的线程。然而,如果要将我的代码组合在一起,我就会失败。我对 WPF 的理解还很遥远。因此,非常感谢您的帮助、建议和评论。
这样的话题确实让我知道应该做什么:
如果在
PasswordBox
中验证了错误,则错误消息不会显示,并且也会显示红色框。我正在验证该属性 Password
是否为空。
如果在
TextBox
上验证了错误,则会显示错误消息,并且不会显示红色框。如果至少在 TextBox
中输入了一个字符,错误消息就会消失。我正在验证该属性 Text
是否为空。
当我用
TextBox
替换 PasswordBox
输入密码时,出现了我当前的问题。然后所有与 TextBox
一起工作的东西都不起作用了。请参阅我的 UserControl
上的实现(下面是未删节的代码):
<TextBox Grid.Column="2" Grid.Row="0"
x:Name="txtUserName"
MinWidth="200"
Text="{Binding UiUserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
<!-- Password components; TODO - the DataErrors are not displayed properly -->
<Label Grid.Column="1" Grid.Row="1" Content="Password" />
<components:BindablePasswordBox Grid.Column="2" Grid.Row="1"
x:Name="txtPassword"
MinWidth="200"
Password="{Binding UiPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
App.xaml
<Application x:Class="PaperDeliveryWpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PaperDeliveryWpf">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/ShellBodyStyle.xaml" />
<ResourceDictionary Source="Resources/ShellFooterStyle.xaml" />
<ResourceDictionary Source="Resources/ShellHeaderStyle.xaml" />
<ResourceDictionary Source="Resources/ShellTitleStyle.xaml" />
<ResourceDictionary Source="Resources/TextBoxStyle.xaml" />
<ResourceDictionary Source="Resources/PasswordBoxStyle.xaml" />
<ResourceDictionary Source="Resources/ButtonStyle.xaml" />
<ResourceDictionary Source="Resources/DataTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
DataTemplate.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:usercontrols="clr-namespace:PaperDeliveryWpf.UserControls"
xmlns:viewmodels="clr-namespace:PaperDeliveryWpf.ViewModels">
<DataTemplate DataType="{x:Type viewmodels:LoginViewModel}">
<usercontrols:LoginUserControl />
</DataTemplate>
</ResourceDictionary>
BindablePasswordBox.xaml
<UserControl x:Class="PaperDeliveryWpf.UserControls.Components.BindablePasswordBox"
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:PaperDeliveryWpf.UserControls.Components"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<PasswordBox x:Name="passwordBox" PasswordChanged="BindablePasswordBox_PasswordChanged"/>
</UserControl>
PasswordBoxStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="PasswordBox">
<!-- Sets basic look of the PasswordBox -->
<Setter Property="Padding" Value="2 1" />
<Setter Property="BorderBrush" Value="LightGray" />
<!-- Removes the red border around the PasswordBox, if validation found errors -->
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<AdornedElementPlaceholder />
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Enables the UI to show the error messages -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
<ItemsControl ItemsSource="{TemplateBinding Validation.Errors}" Margin="0 5 0 5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
BindablePasswordBox.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace PaperDeliveryWpf.UserControls.Components;
public partial class BindablePasswordBox : UserControl
{
private bool _isPasswordChanging;
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(BindablePasswordBox), new PropertyMetadata(string.Empty, PasswordPropertyChanged));
public BindablePasswordBox()
{
InitializeComponent();
}
private void BindablePasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
_isPasswordChanging = true;
Password = passwordBox.Password;
_isPasswordChanging = false;
}
private static void PasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BindablePasswordBox passwordBox)
{
passwordBox.UpdatePassword();
}
}
private void UpdatePassword()
{
if (!_isPasswordChanging)
{
passwordBox.Password = Password;
}
}
}
LoginUserControl.xaml
<UserControl x:Class="PaperDeliveryWpf.UserControls.LoginUserControl"
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:PaperDeliveryWpf.UserControls"
xmlns:viewModels="clr-namespace:PaperDeliveryWpf.ViewModels"
xmlns:components="clr-namespace:PaperDeliveryWpf.UserControls.Components"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:LoginViewModel}"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="boolToVisibility" />
</UserControl.Resources>
<DockPanel>
<!-- Header -->
<Label DockPanel.Dock="Top" Content="Login Page" HorizontalAlignment="Center" FontSize="26" FontWeight="Bold" Margin="20" />
<!-- Body -->
<StackPanel DockPanel.Dock="Top">
<Grid HorizontalAlignment="Center" FocusManager.FocusedElement="{Binding ElementName=txtUserName}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- UserName components -->
<Label Grid.Column="1" Grid.Row="0" Content="User Name" />
<TextBox Grid.Column="2" Grid.Row="0"
x:Name="txtUserName"
MinWidth="200"
Text="{Binding UiUserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
<!-- Password components; TODO - the DataErrors are not displayed properly -->
<Label Grid.Column="1" Grid.Row="1" Content="Password" />
<components:BindablePasswordBox Grid.Column="2" Grid.Row="1"
x:Name="txtPassword"
MinWidth="200"
Password="{Binding UiPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
<!-- Optional design; this is not hiding the entered values in the textbox -->
<!--<TextBox Grid.Column="2" Grid.Row="1"
x:Name="txtPassword"
MinWidth="200"
Text="{Binding UiPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />-->
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Login" Command="{Binding LoginButtonCommand}" />
<Button Content="Cancel" Command="{Binding CancelButtonCommand}" />
</StackPanel>
</StackPanel>
<!-- Footer -->
</DockPanel>
</UserControl>
ShellView.xaml
<Window x:Class="PaperDeliveryWpf.Views.ShellView"
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:PaperDeliveryWpf.Views"
xmlns:viewmodels="clr-namespace:PaperDeliveryWpf.ViewModels"
xmlns:userControls="clr-namespace:PaperDeliveryWpf.UserControls"
Title="ShellView" Height="450" Width="800"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewmodels:ShellViewModel}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="boolToVisibility" />
</Window.Resources>
<Grid>
<!-- *** The main structure of the window is: Titel(Row=0), Header(Row=1), Body(Row=2) and Footer(Row=3) *** -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- *** The UI's body *** -->
<ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- *** The ShellViewModel is hosting the logic, which UserControl shall be loaded *** -->
<StackPanel Grid.Column="1">
<ContentControl Content="{Binding CurrentView, ValidatesOnNotifyDataErrors=False}" />
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</Window>
我认为,您以根本错误的方式执行任务。 PasswordBox 专为安全密码管理而设计。您将其提取到字符串中,从而破坏了所有安全性。从您的实现来看,您只需要屏蔽密码输入。并且无需安全地使用它。在这种情况下,您只需更换 TextBox 中的字体,所有问题都会自动解决。
这种实现的一个例子:
namespace PasswordFont.ViewModels
{
public class LauncherViewModel
{
public string? Password { get; set; }
}
}
<Window.Resources>
<vms:LauncherViewModel x:Key="viewModel" Pin="123456789"/>
<FontFamily x:Key="passowrdFont">pack://application:,,,/Resources/Fonts/#password</FontFamily>
</Window.Resources>
<Window x:Class="PasswordFont.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:PasswordFont"
xmlns:vms="clr-namespace:PasswordFont.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
FontSize="30">
<Window.Resources>
<vms:LauncherViewModel x:Key="viewModel" Password="123456789"/>
<FontFamily x:Key="passowrdFont">pack://application:,,,/Resources/Fonts/#password</FontFamily>
<ImageSource x:Key="open">pack://application:,,,/Resources/Icons/eye_open_icon.png</ImageSource>
<ImageSource x:Key="closed">pack://application:,,,/Resources/Icons/eye_closed_icon.png</ImageSource>
<DataTemplate x:Key="imageDataTemplate" DataType="ImageSource">
<Image Source="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center"
Width="200">
<TextBox x:Name="pinBox" Text="{Binding Pin, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=passwordHide}"
Value="True">
<Setter Property="FontFamily" Value="{DynamicResource passowrdFont}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<ToggleButton HorizontalAlignment="Right"
x:Name="passwordHide"
IsChecked="True"
ContentTemplate="{DynamicResource imageDataTemplate}" Height="{Binding ActualHeight, ElementName=pinBox, Mode=OneWay}">
<FrameworkElement.Resources>
<Style TargetType="ToggleButton">
<Setter Property="ToolTip" Value="Показать"/>
<Setter Property="Content" Value="{DynamicResource open}"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="ToolTip" Value="Скрыть"/>
<Setter Property="Content" Value="{DynamicResource closed}"/>
</Trigger>
</Style.Triggers>
</Style>
</FrameworkElement.Resources>
</ToggleButton>
</Grid>
</Grid>
</Window>
项目源码:PasswordFont.7z
带有解释的主题(我警告你 - 用俄语):具有隐藏/显示密码功能的文本框的实现 [WPF,Eld Hasp]