Laden...

In PasswordBoxen deren Eingabe via WPF vergleichen

Erstellt von sacoma vor einem Jahr Letzter Beitrag vor einem Jahr 764 Views
S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
In PasswordBoxen deren Eingabe via WPF vergleichen

Hallo Leute,

ich will ein User-Login mit Registrierungs-Dialog programmieren.
Im Registrierungs-Dialog gibt es eine Textbox für den Usernamen und zwei Passwort-Textboxen für die Eingabe eines Passworts und für die Wiederholung des Passwortes.

Die Passwortbox für die Passwort-Wiederholung soll roten Hintergrund bekommmen, wenn das Passwort nicht mit dem aus dem 1. Passwortbox übereinstimmt und einen grünen Hintergrund, wenn wenn die Passwörter übereinstimmen.

Ich habe eine Lösung mittels Code-Behind-Programmierung gefunden, wollte aber wissen, ob das auch mit puren WPF-Xaml möglich ist.

Ich habe folgendes in der Xaml-Datei codiert:

             <StackPanel Width="200">
                   <Label Content="Passwort eingeben: " Margin="5,0"/>
                   <PasswordBox x:Name="pbxPwEingabe"  Margin="5,0"/>
                   <Label Content="Passwort wiederholen: " Margin="5,0"/>
                   <PasswordBox  x:Name="pbxPwWiederholen" Margin="5,0">
                       <PasswordBox.Style>
                           <Style TargetType="PasswordBox">
                               <Style.Triggers>
                                   <DataTrigger Binding="{Binding ElementName=pbxPwWiederholen, Path=Text}" 
                                   Value="{Binding ElementName=pbxPwEingabe, Path=Text}">
                                       <Setter Property="Background" Value="LightGreen"/>
                                   </DataTrigger>
                               </Style.Triggers>
                           </Style>
                       </PasswordBox.Style>
                   </PasswordBox>
                   <Button x:Name="btnRegistrieren" Content="Registrieren" Margin="5"/>
               </StackPanel>

Mein Problem: im Tag des "DataTrigger" wird das Attribut "Value" unterkringelt und es wird folgender Fehler in der Error-List angezeigt:

Error XDG0010 "Binding" ist keine gültige Auslöserbedingung... - ich habe die Fehlermeldung gegoogelt, aber nichts gefunden.

Ich weiß nur, dass ich in "Value" etwas anderes einfügen muss. Die Frage ist was?

Kann mir jemand helfen? - Danke für die Hilfe im Voraus. 😉

LG,

sacoma

2.079 Beiträge seit 2012
vor einem Jahr

"Value" aktzeptiert kein Binding, das muss ein fest im Code stehender Wert sein.

Was Du braucht ist MVVM.
Definiere im ViewModel zwei Properties für die Passwörter und eine bool-Property, die true ist, wenn die Passwörter sich unterscheiden.
Dann kannst Du an die bool-Property binden und für "Value" trägst Du "True" ein.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Palladin007,

man kann das alles nicht allein im WPF codieren, sondern braucht diese Properties im ViewModel...

OK, ich werde es mal umsetzen.

Danke für deine Antwort. 😉

LG,

sacoma

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
Rückmeldung

Hallo,

ich habe Palladin007's Idee umgesetzt.

Hier das Ergebnis:
Bei Textboxen funktioniert es gut. 
Aber bei PasswordBox funktioniert es nicht; der Grund: man kann die PasswordBox nicht auf eine Property in der ViewModel binden.

LG,

sacoma 😉

2.079 Beiträge seit 2012
vor einem Jahr

Ich erinnere mich, da war ja was ^^

Dann geht's nicht ohne CodeBehind bzw. lohnt sich nicht.

MVVM lohnt sich aber auf jeden Fall, vorausgesetzt, Du ziehst es durch deine gesamte UI durch.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (sender is not PasswordBox pwBox)
        return;
        
    if (DataContext is not MyViewModel viewModel)
        return;
        
    viewModel.Password = pwBox.Password;
}

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

187 Beiträge seit 2009
vor einem Jahr

Servus, eine nicht ganz MVVM konforme Lösung

    <Window.Resources>
        <Style x:Key="PasswordBoxStyle"
               TargetType="PasswordBox">
            <Setter Property="Background" Value="LightGreen"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding PasswordsAreEqual}" Value="{x:Static sys:Boolean.FalseString}">
                    <Setter Property="Background" Value="Red" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <PasswordBox x:Name="PasswordBox1"
                     Grid.Row="0"
                     Tag="Password1"
                     PasswordChar="*"
                     Style="{StaticResource PasswordBoxStyle}">
            <behav:Interaction.Triggers>
                <behav:EventTrigger EventName="PasswordChanged">
                    <behav:InvokeCommandAction Command="{Binding PasswordChanged, Mode=OneWay}" 
                                               CommandParameter="{Binding ElementName=PasswordBox1}" />
                </behav:EventTrigger>
            </behav:Interaction.Triggers>
        </PasswordBox>

        <PasswordBox x:Name="PasswordBox2"
                     Grid.Row="1"
                     Tag="Password2"
                     PasswordChar="*"
                     Style="{StaticResource PasswordBoxStyle}">
            <behav:Interaction.Triggers>
                <behav:EventTrigger EventName="PasswordChanged">
                    <behav:InvokeCommandAction Command="{Binding PasswordChanged, Mode=OneWay}" 
                                               CommandParameter="{Binding ElementName=PasswordBox2}" />
                </behav:EventTrigger>
            </behav:Interaction.Triggers>
        </PasswordBox>
    </Grid>
    public class MainViewModel : ViewModelBase
    {
        #region Fields
        private bool passwordsAreEqual;
        #endregion

        #region Properties
        public SecureString Password1 { get; set; }

        public SecureString Password2 { get; set; }

        public bool PasswordsAreEqual
        {
            get { return passwordsAreEqual; }
            set
            {
                if (passwordsAreEqual != value)
                {
                    passwordsAreEqual = value;
                    OnPropertyChanged();
                }
            }
        }

        public ICommand PasswordChanged { get; private set; }
        #endregion

        #region Constructors
        public MainViewModel()
        {
            PasswordChanged = new RelayCommand(OnPasswordChangedExecuted);
        }
        #endregion

        #region Methods
        private void OnPasswordChangedExecuted(object obj)
        {
            if (obj is PasswordBox passwordBox)
            {
                if (passwordBox.Name.Equals("PasswordBox1"))
                {
                    Password1 = passwordBox.SecurePassword;
                }
                else
                {
                    Password2 = passwordBox.SecurePassword;
                }
            }

            PasswordsAreEqual = SecureStringToPlainString(Password1).Equals(SecureStringToPlainString(Password2)) ? true : false;
        }
        #endregion
    }
2.079 Beiträge seit 2012
vor einem Jahr

nicht ganz MVVM konforme

Warum?

Ist absolut MVVM konform.
Meine Lösung ist auch MVVM konform.

Problematisch finde ich allerdings die Komplexität, dass man ein NuGet-Package, einen Command und einen Magic-String, nur um keinen CodeBehind nutzen zu müssen.

Dass man keinen CodeBehind nutzen darf, ist ein Irrglaube, es geht viel mehr darum, wofür man CodeBehind nutzt, also Business-Logik vs. UI-Logik.
Hier geht es nur darum, fehlenden DataBinding-Support nachzurüsten, das fällt unter UI-Logik.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

126 Beiträge seit 2023
vor einem Jahr

@Palladin007

Genau, die View darf so viel Code haben wie sie möchte, solange sich dieser Code nur um die Darstellung kümmert.

Das mit der Komplexität (ein NuGet-Package und ein Command) sehe ich nicht so, denn in meinen MVVM-Projekten verwende ich immer ReactiveUI und warum sollte ich das für so eine Lösung nicht verwenden? Es sieht also nur scheinbar komplex aus aber im Gesamtbild macht es keinen Unterschied.

Ich hätte dieses mit einer AttachedProperty gelöst, um die Bindung sehr elegant rein in XAML zu definieren. Dadurch kann ich den zugrunde liegenden Code auch noch wiederverwenden - wenn man den in ein eigenens NuGet-Package verfrachtet sogar für mehrere Projekte.

So sähe das also bei mir aus:

<DockPanel>
    <Label Content="Password:"
           DockPanel.Dock="Top" />
    <PasswordBox utils:PasswordBoxHelper.BindableSecurePassword="{Binding Password1}"
                 utils:PasswordBoxHelper.IsActive="True">
        <PasswordBox.Style>
            <Style TargetType="PasswordBox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding PasswordIsValid}"
                                 Value="True">
                        <Setter Property="Background"
                                Value="LightGreen" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </PasswordBox.Style>
    </PasswordBox>
</DockPanel>
<DockPanel>
    <Label Content="Passsword (retype):"
           DockPanel.Dock="Top" />
    <PasswordBox utils:PasswordBoxHelper.BindableSecurePassword="{Binding Password2}"
                 utils:PasswordBoxHelper.IsActive="True">
        <PasswordBox.Style>
            <Style TargetType="PasswordBox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding PasswordsMatch}"
                                 Value="True">
                        <Setter Property="Background"
                                Value="LightGreen" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </PasswordBox.Style>
    </PasswordBox>
</DockPanel>

und das ViewModel

public class RegisterWindowViewModel : ReactiveObject
{
    private readonly ObservableAsPropertyHelper<bool> _usernameIsValid;
    private readonly ObservableAsPropertyHelper<bool> _passwordIsValid;
    private readonly ObservableAsPropertyHelper<bool> _passwordsMatch;

    public RegisterWindowViewModel()
    {
        _usernameIsValid = this.WhenAnyValue(
            e => e.Username,
            ( u ) => !string.IsNullOrEmpty( u ) && u.Length >= 1 ) // darf auch gerne komplexer sein
            .ToProperty( this, e => e.UsernameIsValid );

        _passwordIsValid = this.WhenAnyValue( 
            e => e.Password1, 
            ( p ) => p is not null && p.Length >= 1 ) // darf auch gerne komplexer sein
            .ToProperty( this, e => e.PasswordIsValid );

        _passwordsMatch = this.WhenAnyValue(
            e => e.PasswordIsValid,
            e => e.Password1,
            e => e.Password2,
            ( p1valid, p1, p2 ) => p1valid && p1 is not null && p2 is not null && p1.Length == p2.Length && p1.IsEqualTo( p2 ) )
            .ToProperty( this, e => e.PasswordsMatch );

        var canRegister = this.WhenAnyValue(
            e => e.UsernameIsValid,
            e => e.PasswordsMatch,
            ( u, p ) => u && p );
        Register = ReactiveCommand.CreateFromTask( OnRegisterAsync, canRegister );
    }

    private Task OnRegisterAsync( CancellationToken cancellationToken )
    {
        /// 
        /// Hier die Registrierung durchführen
        ///
        Username = string.Empty;
        Password1 = null;
        Password2 = null;
        return Task.CompletedTask;
    }

    [Reactive] public string Username { get; set; } = string.Empty;
    public bool UsernameIsValid => _usernameIsValid.Value;
    [Reactive] public SecureString? Password1 { get; set; }
    public bool PasswordIsValid => _passwordIsValid.Value;
    [Reactive] public SecureString? Password2 { get; set; }
    public bool PasswordsMatch => _passwordsMatch.Value;
    public ReactiveCommand<Unit, Unit> Register { get; }
}

und noch der PasswordBoxHelper

public static class PasswordBoxHelper
{
    public static SecureString GetBindableSecurePassword( PasswordBox obj )
    {
        return (SecureString)obj.GetValue( BindableSecurePasswordProperty );
    }

    public static void SetBindableSecurePassword( PasswordBox obj, SecureString value )
    {
        var old = obj.GetValue( BindableSecurePasswordProperty ) as SecureString;
        obj.SetValue( BindableSecurePasswordProperty, value );
        old?.Dispose();
    }

    public static readonly DependencyProperty BindableSecurePasswordProperty =
        DependencyProperty.RegisterAttached( "BindableSecurePassword", typeof( SecureString ), typeof( PasswordBoxHelper ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnBindableSecurePasswordChanged ) );

    private static void OnBindableSecurePasswordChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var pb = (PasswordBox)d;
        if ( e.NewValue is null )
            pb.Clear();
    }

    public static bool GetIsActive( PasswordBox obj )
    {
        return (bool)obj.GetValue( IsActiveProperty );
    }

    public static void SetIsActive( PasswordBox obj, bool value )
    {
        obj.SetValue( IsActiveProperty, value );
    }

    public static readonly DependencyProperty IsActiveProperty =
        DependencyProperty.RegisterAttached( "IsActive", typeof( bool ), typeof( PasswordBoxHelper ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsActiveChanged ) );

    private static void OnIsActiveChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var pb = (PasswordBox)d;

        if ( (bool)e.OldValue )
        {
            pb.PasswordChanged -= PasswordChanged;
        }

        if ( (bool)e.NewValue )
        {
            pb.PasswordChanged += PasswordChanged;
        }
    }

    private static void PasswordChanged( object sender, RoutedEventArgs e )
    {
        var password = (PasswordBox)sender;
        SetBindableSecurePassword( password, password.SecurePassword.Copy() );
    }
}

Hat die Blume einen Knick, war der Schmetterling zu dick.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Leute,

danke für eure Beiträge - ich werde mir alles in ruhig ansehen und ausprobieren.

LG,

sacoma 😉

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
Eure Meinung dazu

Hallo Leute,

nochmals danke für eure Beiträge.

Ich habe einen Freunde, der sich hobbymäßig auch mit C# und WPF beschäftigt. Er hatte mir folgendes vorgeschlagen:

Die in den WPF-PasswordBoxen eingegebene Passwörter im Code-Behind auslesen, auf Übereinstimmung prüfen und wenn alles richtig ist, Passwort verschlüsseln. Danach das verschlüsselte Passwort in einen unsichtbaren TextBlock auf der WPF-View übergeben.

Der unsichtbare TextBlock ist an einer Property aus dem ViewModel gebindet (Mode: TwoWay), so kann der verschlüsselte Passwort-String der ViewModel übergeben werden.

Was haltet ihr von dieser Lösung?

Bin auf eure Meinungen gespannt. 😉

LG,

sacoma

2.079 Beiträge seit 2012
vor einem Jahr

Was haltet ihr von dieser Lösung?

Nicht viel 😉

Klingt als hätte dieser Freund seine Erfahrungen hauptsächlich mit HTML/CSS und JavaScript gemacht, da habe ich sowas schon das eine oder andere mal gesehen.

Die in den WPF-PasswordBoxen eingegebene Passwörter im Code-Behind auslesen

Soweit so normal, muss man aber nicht "auslesen", dafür gibt's das PasswordChanged-Event, was ich eingangs erwähnt habe.

auf Übereinstimmung prüfen

Soweit auch normal, allerdings gehört das mMn. in's ViewModel.

Passwort verschlüsseln

Unsinnig.
Der Key muss auch irgendwo stehen, für einen Angreifer ist das also quasi Freitext.
Dann einfach den SecureString verwenden, auch der ist nicht sicher, aber immer noch besser, als selber gebastelte "Sicherheit", die keine ist.

das verschlüsselte Passwort in einen unsichtbaren TextBlock auf der WPF-View übergeben

Wozu? Es ist eine objektorientierte Programmiersprache, man kann das Passwort auch mit einer normalen Variable im RAM behalten - wieder ein Fall für's ViewModel. Geht natürlich auch im CodeBehing, sollte aber nicht.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.834 Beiträge seit 2008
vor einem Jahr

Um ne zweite Antwort zu haben.

Zitat von Palladin007

Was haltet ihr von dieser Lösung?

Nicht viel 😉

Dem, sowie dem Rest, stimm ich zu.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Leute,

danke für eure Meingung.

Mir fällt erst jetzt auf, dass ich vergessen hatte zuschreiben, dass in der ViewModel noch ein EventAggregator (vom Nuget-Paket "Prism") implementiert ist - das heißt, eine Instanzierung der ViewModel ist so nicht möglich...

LG,

sacoma

126 Beiträge seit 2023
vor einem Jahr

Es ist doch für diese Frage völlig unerheblich, was du warum auch immer noch zusätzlich benötigst - für deine hier gestellte Frage spielt der EventAggregator keine Rolle.

Wenn du eine Frage zum Prism-Eventaggregator-ViewModel-Constructor hast, dann kannst du ja eine neue Frage stellen.

Hat die Blume einen Knick, war der Schmetterling zu dick.

2.079 Beiträge seit 2012
vor einem Jahr

Du sollst im CodeBehind gar nicht das ViewModel instanziieren.
Guck dir meinen Code ganz zu Beginn an, siehst Du da irgendwo ein new myViewModel()?

Du musst dem DataContext auf irgendeine Weise eine ViewModel-Instanz zuweisen.
Meist beginnt das bei MainWindow manuell, danach gibt's viele verschiedene Wege, z.B. klassisches Binding, oder Prism bietet irgendwas eigenes an, aber den DataContext brauchst Du immer.

Ach ja:

[Artikel] MVVM und DataBinding

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Palladin007,

Habe ich das richtig verstanden? Die ViewModel wird nicht instanziiert?
Aber was bedeutet dann die Zeile:

viewModel.Password = pwBox.Password;

im Code:

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (sender is not PasswordBox pwBox)
return;

if (DataContext is not MyViewModel viewModel)
return;

viewModel.Password = pwBox.Password;
}

"viewModel" scheint mir der Objekt-Variablen-Namen zu sein, der z.B. im Konstruktor mit "ViewModel viewModel = new ViewModel();" instanziiert wurde.

Ok, ich bin Anfänger, es kann sein, dass ich die Zeile falsch interpretiert habe.

LG,

sacoma

2.079 Beiträge seit 2012
vor einem Jahr

Entweder, Du bist noch sehr viel mehr Anfänger, als dir lieb ist, oder Du kennst kein Pattern Matching.

Bevor Du mit WPF und/oder MVVM arbeitest, sollten die Sprachgrundlagen auf jeden Fall sitzen, beides parallel lernen halte ich für unrealistisch.

Und Pattern Matching:

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching

Oder aus meinem Code:

if (DataContext is not MyViewModel viewModel)
    return;

... wird vom Compiler umgeformt zu ...

MyViewModel viewModel = DataContext as MyViewModel;

if (viewModel is null)
	return;

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
Problem gelöst und Danke an alle für Ihre Hilfe!

Hallo Palladin007,

ja, ich bin Anfänger - ich beschäftigte mich erst seit ca. 4 Monaten mit C# und WPF und mir ist klar, dass ich noch viel lernen muss... 😉

@all:
Danke an alle für Ihre hilfreichen Beiträge - mein Problem konnte ich mit eurer Hilfe lösen. 😄

LG,

sacoma

2.079 Beiträge seit 2012
vor einem Jahr

mir ist klar, dass ich noch viel lernen muss

Du verstehst nicht - lass WPF erst einmal liegen und arbeite die Grundlagen mit simplen Konsolenprogrammen durch.

Wie gesagt:
Bevor Du WPF und MVVM lernen willst, sollten die Grundlagen fest sitzen.
Das gilt für alle diese Frameworks, WPF (oder Avalonia, UWP, MAUI, ASP.NET Core, etc.) sind sehr komplex, Du tust dir keinen Gefallen, wenn Du vorschnell damit beginnst.

Du kannst ja später dort weitermachen, wo Du aufgehört hast, oder Du setzt dein Projekt als Konsolen-Anwendung um und baust es dann Stück für Stück in Richtung WPF um. So sammelst Du auch gleich ein paar Erfahrungen zum Thema Architektur - schlechte Architektur-Entscheidungen fallen bei solchen Umbauten auf.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.