如何让PasswordBox在错误验证时显示错误消息(并删除红色框)?

问题描述 投票:0回答:1

有几个关于错误验证的线程。然而,如果要将我的代码组合在一起,我就会失败。我对 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>

c# wpf xaml mvvm
1个回答
0
投票

我认为,您以根本错误的方式执行任务。 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]

© www.soinside.com 2019 - 2024. All rights reserved.