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 😃
Falsches Forum, sorry. Bitte verschieben in WPF und XAML
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.
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>
Warum verwendest du eine statische Ressource für das ViewModel, anstatt eine Instanz davon an den DataContext
der Klasse zu binden?
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.
Also an dem ToggleButton liegt es definitiv nicht. Siehe Projekt im Anhang.
Hat die Blume einen Knick, war der Schmetterling zu dick.
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.
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.
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.
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.
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.
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!