Laden...

Visible Eigenschaft setzen über mehrere ViewModels

Erstellt von theSoulT vor 7 Monaten Letzter Beitrag vor 7 Monaten 503 Views
T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten
Visible Eigenschaft setzen über mehrere ViewModels

Hallo zusammen,

ich bin grad in der Erstellung eines Tools. Dieses hat mehrer ViewModels passend zu den Views.

In dem View "BasicWindowView" habe ich einen ToggleButton, welcher anfangs nicht sichtbar sein soll. Erst wenn der User auf den Login Button in der LoginView klickt, soll der ToggleButton sichtbar werden. Dazu habe ich eine Visibility-Variable "MenuVisibility" in der BaseViewModel angelegt, damit diese in allen ViewModels verfügbar ist. Gebindet habe ich den ToggleButton dann aber auf die LoginVM.MenuVisibility Eigenschaft.

Kann man das so machen?

Die MenuVisibility wird im Konstruktor von LoginVM direkt auf Collapsed gesetzt.

Nun habe ich per Command eine Methode in der LoginVM aufgerufen, welche die MenuVisibility auf Visible setzt. Leider ist der Button dauerhaft sichtbar, beim Debuggen wird die Variable aus mir unerklärlichen Gründen immer wieder auf Visible gesetzt.

	public class LoginVM : BaseViewModel, INotifyPropertyChanged
	{ 
        public LoginVM()
        {
            LoginCommand = new LoginCommand(this);
            ActualUser = new User();
            Username = "m.mustermann";
            MenuVisibility = Visibility.Collapsed;
        }

        public void LoggingIn()
        {
            MenuVisibility = Visibility.Visible;
        }
        
        ...
	}
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        private Visibility menuVisibility;
        public Visibility MenuVisibility
        {
            get { return menuVisibility; }
            set 
            { 
                menuVisibility = value;
                OnPropertyChanged();
            }
        }
        
        ...
    }
<ToggleButton HorizontalAlignment="Left" Width="50" Visibility="{Binding Source={StaticResource LoginVM}, Path=MenuVisibility, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" Command="{Binding OpenMainMenuCommand}">
...

Könnt ihr mir da einen Tipp geben, was das Problem ist, bzw wie ich das eleganter lösen kann?

Danke schonmal 😃

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Falsches Forum, sorry. Bitte verschieben in WPF und XAML

124 Beiträge seit 2023
vor 7 Monaten

Dein Hauptproblem ist der Binding-Mode OneWayToSource

Siehe dazu Übersicht über Datenbindung - Richtung des Datenflusses

So sollte das funktionieren

<ToggleButton 
  HorizontalAlignment="Left" 
  Width="50" 
  Visibility="{Binding Source={StaticResource LoginVM}, Path=MenuVisibility, Mode=OneWay}" 
  Command="{Binding OpenMainMenuCommand}">

Das Setzen von UpdateSourceTrigger ist überflüssig, denn eine Änderung der Eigenschaft erfolgt doch gar nicht über die View.

Das Setzen von Mode ist auch überflüssig, da es sich bei OneWay um den Default-Wert handelt.

Darüber hinaus sollte man im ViewModel keine Typen vom UI-Layer/-Framework verwenden (wie z.B. hier Visibility). Hier macht es wesentlich mehr Sinn im ViewModel eine Eigenschaft vom Typbool zu haben (denkbarer Name wäre IsAuthenticated) und der Bindung dann einen Converter an die Hand zu geben, der diesen bool Wert in den passenden Visibility Wert konvertiert. Damit lässt man dem UI-Designer alle Freiheiten zur Darstellung.

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

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Danke für deine Rückmeldung 😃

Ich hab es auhc schon mit einem Converter versucht tatsächlich. Selbes Ergebnis. Dauerhaft wird der Button angezeigt.

Wenn ich den Mode ändere, wird der Button nie angezeigt 😅 Beides nicht so optimal.

Kann das Problem eventuell an der Implementierung des ToggleButton zu tun haben?

            <ToggleButton  
            HorizontalAlignment="Left"  
            Width="50"  
            Visibility="{Binding Source={StaticResource LoginVM}, 
            Path=IsAuthenticated, 
            Converter={StaticResource BooleanToVisibilityConverter}}" 
            Command="{Binding OpenMainMenuCommand}">
                <ToggleButton.Template>
                    <ControlTemplate TargetType="ToggleButton">
                        <Border 
                        x:Name="border" 
                        Background="{DynamicResource darkgray}" 
                        CornerRadius="5" 
                        Margin="5">
                            <iconPacks:PackIconMaterial 
                            x:Name="icon" 
                            Kind="Menu" 
                            HorizontalAlignment="Center" 
                            VerticalAlignment="Center" 
                            Foreground="{DynamicResource yellow}"/>
                        </Border>
                    </ControlTemplate>
                </ToggleButton.Template>
                <ToggleButton.Triggers>
                    <EventTrigger 
                    RoutedEvent="ToggleButton.Unchecked">
                        <BeginStoryboard>
                            <Storyboard 
                            x:Name="HideStackPanel">
                                <DoubleAnimation 
                                Storyboard.TargetName="navPanel" 
                                Storyboard.TargetProperty="Width" 
                                BeginTime="0:0:0" 
                                From="200" 
                                To="0" 
                                Duration="0:0:0:0.3"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <EventTrigger 
                    RoutedEvent="ToggleButton.Checked">
                        <BeginStoryboard>
                            <Storyboard 
                            x:Name="ShowStackPanel">
                                <DoubleAnimation 
                                Storyboard.TargetName="navPanel" 
                                Storyboard.TargetProperty="Width" 
                                BeginTime="0:0:0" 
                                From="0" 
                                To="200" 
                                Duration="0:0:0:0.3"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </ToggleButton.Triggers>
            </ToggleButton>
4.917 Beiträge seit 2008
vor 7 Monaten

Warum verwendest du eine statische Ressource für das ViewModel, anstatt eine Instanz davon an den DataContext der Klasse zu binden?

s.a. [Artikel] MVVM und DataBinding

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Hatte das bei einem online Kurs so gelernt. Wenn der DataContext auf einer andern VM ist, als im übergeordneten Control, dann kann man so einen anderen DataContext nutzen.

124 Beiträge seit 2023
vor 7 Monaten

Also an dem ToggleButton liegt es definitiv nicht. Siehe Projekt im Anhang.

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

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Der Unterschied bei deinem Projekt ist, dass der ToggleButton und der LoginButton in einer View sind. Bei mir sind das zwei Views. Mein ToggleButton sitzt in einer anderen View wie der Login Button.

124 Beiträge seit 2023
vor 7 Monaten

Das ist der Logik hinter dem ToggleButton völlig egal, von wem und woher er die Information bekommt, er muss sie nur bekommen und die Benachrichtigung muss funktionieren, dann arbeitet der wie zu erwarten und genau das sollte das Beispielprojekt zeigen (nicht mehr).

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

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Ja das glaub ich dir gern. Allerdings funktioniert es nicht. Hab das Projekt mal in einfach nachgebaut. Könntest du dir das bitte mal anschauen? Wenn man das so startet ist der ToggleButton oben schon ausgeblendet. Hab das OnPropertyChanged nicht ausgeführt. Funktioniert aber trotzdem nicht.

187 Beiträge seit 2009
vor 7 Monaten

Ist schon spät heute, aber nur soviel. Das LoginVM wird mehrfach instanziert. Ist zwar jetzt nicht die Lösung aber nur so als Hinweis.

124 Beiträge seit 2023
vor 7 Monaten

Du arbeitest mit DREI Instanzen vom LoginVM.

Die erste Instanz erzeugst du im BasicWindowVM

namespace WpfVisible.ViewModel
{
    public class BasicWindowVM : BaseViewModel
    {
        public BaseViewModel ViewModel { get; set; }

        public BasicWindowVM()
        {
            ViewModel = new LoginVM(); // <<== ERSTE Instanz
        }
    }
}

und die zweite Instanz in der BasicWindowView

<Window x:Class="WpfVisible.View.BasicWindowView"
        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:WpfVisible.View"
        xmlns:vm="clr-namespace:WpfVisible.ViewModel"
        xmlns:converter="clr-namespace:WpfVisible.ViewModel.Converters"
        mc:Ignorable="d"
        Title="BasicWindowView"
        Height="450"
        Width="800">
    <Window.DataContext>
        <vm:BasicWindowVM />
    </Window.DataContext>
    <Window.Resources>
        <!-- ZWEITE Instanz -->
        <vm:LoginVM x:Key="LoginVM" />
        <converter:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <DockPanel>
            <ToggleButton DockPanel.Dock="Top"
                          Content="Toggle"
                          Visibility="{Binding Source={StaticResource LoginVM}, Path=IsAuthenticated, Mode=OneWayToSource, Converter={StaticResource BooleanToVisibilityConverter}}" />
            <!--Visibility="{Binding Source={StaticResource LoginVM}, Path=IsAuthenticated, Converter={StaticResource BooleanToVisibilityConverter}}"-->
            <ContentControl Content="{Binding ViewModel}" />
        </DockPanel>
    </Grid>
</Window>

und die dritte im LoginView

<UserControl x:Class="WpfVisible.View.LoginView"
             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:WpfVisible.View" 
             xmlns:vm="clr-namespace:WpfVisible.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.DataContext>
        <!-- DRITTE Instanz -->
        <vm:LoginVM/>
    </UserControl.DataContext>
    <Grid>
        <Button Content="Login" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Command="{Binding LoginCommand}"/>
    </Grid>
</UserControl>

Der Login-Button ist mit der dritten Instanz verknüpft und der ToggleButton mit der zweiten Instanz. Da ist es auch kein Wunder, dass dann nichts passiert.

Das Positive: Mehr falsch machen kann man fast nicht mehr.

Hier nun die Änderungen, die notwendig sind:

BasicWindowView.xaml

<Window x:Class="WpfVisible.View.BasicWindowView"
        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:WpfVisible.View"
        xmlns:vm="clr-namespace:WpfVisible.ViewModel"
        xmlns:converter="clr-namespace:WpfVisible.ViewModel.Converters"
        mc:Ignorable="d"
        Title="BasicWindowView"
        Height="450"
        Width="800">
    <Window.Resources>
        <converter:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Window.DataContext>
        <vm:BasicWindowVM />
    </Window.DataContext>
    <Grid>
        <DockPanel>
            <ToggleButton DockPanel.Dock="Top"
                          Content="Toggle"
                          Visibility="{Binding ViewModel.IsAuthenticated, Converter={StaticResource BooleanToVisibilityConverter}}" />
            <ContentControl Content="{Binding ViewModel}" />
        </DockPanel>
    </Grid>
</Window>

LoginView.xaml

<UserControl x:Class="WpfVisible.View.LoginView"
             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:WpfVisible.View"
             xmlns:vm="clr-namespace:WpfVisible.ViewModel"
             mc:Ignorable="d"
             d:DesignHeight="450"
             d:DesignWidth="800">
    <Grid>
        <Button Content="Login"
                VerticalAlignment="Center"
                HorizontalAlignment="Center"
                Width="200"
                Command="{Binding LoginCommand}" />
    </Grid>
</UserControl>

BaseViewModel.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfVisible.ViewModel
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
        {
            PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }

        private bool isAuthenticated;

        public bool IsAuthenticated
        {
            get { return isAuthenticated; }
            set { isAuthenticated = value; OnPropertyChanged(); }
        }

    }
}

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

T
theSoulT Themenstarter:in
64 Beiträge seit 2018
vor 7 Monaten

Doch, das war die Lösung. Ich danke euch 😀 Bin halt leider noch Anfänger und in den Onlinekursen lernt man nur Grundlagen. 😅

Vielen Dank!