Gewinde Geschrieben 14. Dezember 2023 Geschrieben 14. Dezember 2023 Hallo Leute, ich möchte in meinem WPF Projekt diverse ToggleButton nutzen und stoße dabei leider auf einige Probleme in Bezug auf MVVM. Ich habe 2 ObservableCollection und 3 - 4 ToggleButton in meiner View. Die erste Collection stellt dem Benutzer vorher ausgewählte FileInfo Objekte zur verfügung. Diese Auswahl wird über normale Button mit Commandbinding erreicht. Die zweite Collection soll zur Verfeinerung dienen. Dabei kommen die ToggleButton ins Spiel. Jeder ToggleButton steht dabei für eine bestimmte Art von Datei. In meiner Idee ermöglicht die IsChecked oder IsUnChecked Property der ToggleButton dem Benutzer die verfeinerte Anzeige in der zweiten Collection (Dies muss über File.Name.Contains(xy) erreicht werden ). Ich habe es bis jetzt mit einer Variable (bool) im ViewModel versucht, was nicht wirklich zum Erfolg geführt hat (oder nur teilweise). Dabei war der Gedanke, dass die IsChecked Property true oder false durch ein Binding ermöglicht. Zusätzlich habe ich ein Binding auf ein Command, welches im Konstruktor zugeordnet wird und die Logik für die IsChecked Property und den auszuführenden Code auslöst. Da ich allerdings mehr als 1 ToggleButton und mehr als eine Art File habe, würde ich viele verschiedene Properties und Commands benötigen. Dabei wird allerdings in jedem Fall fast der gleiche Code ausgeführt. Aus diesem Grund bin ich nicht davon überzeugt, dass dies der richtige Lösungsansatz ist. In einer weiteren überlegung habe ich Events in betracht gezogen, da sich ja die Funktionsweise der Togglebutton dafür gut eignen müsste. Dabei habe ich allerdings das Problem ( außer dem Problem das mich Events und EventHandler noch recht verwirren), wo behandle ich das ausgelöste Event? Ich habe mal gelesen, dass die Behandlung in der Klasse erfolgen sollte, welche das Event auslöst. Wäre in dem Fall die View. Da ich in einer View kein Event behandeln kann, würde der nächste Gedanke in Richtung CodeBehind gehen. Wenn ich jetzt die Behandlung im Codebehind habe, würde ich doch gegen das MVVM verstoßen? Die nächste Frage wäre, wo binde ich überhaupt das Command bzw. nutzt man dann überhaupt noch ein Command? Löse ich dieses im ViewModel aus, sobald das Event vom ToggleButton ausgelöst wird? Ich finde leider keine guten Erklärungen für die Bindung von ToggleButton im MVVM Pattern. Die meisten zeigen die Bindung im Codebehind mit der einfachen Angabe eines Events, welches das Fenster hell oder dunkel werden lässt (nicht hilfreich ). Andere wiederum sind ( für mich) so unübersichtlich dargestellt, dass ich dem Codebeispiel nicht wirklich folgen kann. Vielleicht könnte mir einer von euch wieder etwas auf die Sprünge helfen und mir einen Weg in die richtige Richtung aufzeigen. Danke euch Zitieren
Tratos Geschrieben 15. Dezember 2023 Geschrieben 15. Dezember 2023 (bearbeitet) Zitat using System; using System.Collections.Generic; // Part 1: create a List of KeyValuePairs. var list = new List<KeyValuePair<string, int>>(); list.Add(new KeyValuePair<string, int>("toggle", 1)); list.Add(new KeyValuePair<string, int>("toggle2", 0)); list.Add(new KeyValuePair<string, int>("toggle3", 1)); // Part 2: loop over list and print pairs. foreach (var element in list) { Console.WriteLine(element); } [toggle, 1] [toggle2, 0] [toggle3, 1] Bearbeitet 15. Dezember 2023 von Tratos Zitieren
Gewinde Geschrieben 15. Dezember 2023 Autor Geschrieben 15. Dezember 2023 Guten Morgen Tratos, Vielleicht sind meine Angaben etwas missverständlich. In den Listen werden Daten angezeigt, welche aus einer Ordnerstruktur gelesen werden. Diese müssen für den E-Mail Versand vorbereitet werden. Der Abruf kann nur über den File.Name stattfinden. Mit den Togglebutton versuche ich diesen Abruf zu realisieren. Z.B. Togglebutton "ABCD" IsChecked = Files mit "ABCD" im Namen werden der zweiten Liste hinzugefügt. Wenn man den gleichen Togglebutton erneut drückt, müssen diese Files aus der zweiten Liste wieder verschwinden. Grüße Zitieren
Gewinde Geschrieben 15. Dezember 2023 Autor Geschrieben 15. Dezember 2023 Ich habe gerade einen Post im Netz gefunden, dort wird die Umsetzung über ein Template mittels Trigger erreicht. Wäre dies die oder eine gängige Lösung für mein Problem? In diesem Fall würde ein Trigger das Command auslösen. Wenn ich das richtig verstehe, wird die IsChecked Property auf die Command Property gebunden? Ist das Korrekt? Zitieren
eatenbaog Geschrieben 15. Dezember 2023 Geschrieben 15. Dezember 2023 Hi @Gewinde ich arbeite schon länger mit WPF, bin mir aber nicht ganz sicher, ob ich dein Problem richtig verstehe. Vielleicht könntest du noch etwas Code mit deinem derzeitigen Ansatz bzw. deiner derzeitigen Lösung ergänzen. In meinem Verständnis könntest du einfach bei deinen ToggleButtons den Command auf ein Command deines ViewModels binden und dort dann, je nachdem welcher ToggleButton geklickt wurde, dynamisch deinen FileName deiner Datei per CommandParameter übergeben. Das gebindete Command im ViewModel würde dann mit deinem übergebenem FileName entsprechend deinen Code ausführen. Hier ein Beispiel (wobei mir nicht ganz klar ist, wofür du die IsChecked-Property benötigst, deshalb habe ich die rausgelassen). WPF-Code (Hier könntest du ggfs. die FileNames-Property deiner Liste auch auf den CommandParameter binden, anstatt wie ich hier einen festen Parameter definieren.) : <ToggleButton Command="{Binding TestCommand}" CommandParameter="Test"> </ToggleButton> C# ViewModel-Code: private ICommand _testCommand; public ICommand TestCommand { get { _testCommand ??= new RelayCommand(param => testFunc(param as string)); return _testCommand; } } public void testFunc(string fileName) { Console.WriteLine(fileName); } Zitieren
Gewinde Geschrieben 15. Dezember 2023 Autor Geschrieben 15. Dezember 2023 (bearbeitet) Hey, ich zeige einfach mal was ich bis jetzt habe: FrontEnd XAML, der Startbutton dient z.Z. nur zum Füllen der Liste um den Test zu starten, dies wird später über eine eigene Klasse mit gewünschtem Datenabruf stattfinden. Die ToggleButton sind an das Command im ViewModel gebunden (wie ein normaler Button). <Window.DataContext> <vm:UserViewModel/> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <ListBox x:Name="LeftBox" Grid.Column="0" Margin="5" ItemsSource="{Binding DataList}"> </ListBox> <ListBox x:Name="RightBox" Grid.Column="1" Margin="5" ItemsSource="{Binding FiltererdDataList}"> </ListBox> <StackPanel Grid.Column="2"> <Button x:Name="Start" Content="Start" Height="30" Width="90" Margin="5" Command="{Binding StartAppCommand}"> </Button> <ToggleButton x:Name="ABCD" Content="ABCD" Height="30" Width="90" Command="{Binding FilterDataCommand}" CommandParameter="ABCD"> </ToggleButton> <ToggleButton x:Name="EFGH" Content="EFGH" Height="30" Width="90" Command="{Binding FilterDataCommand}" CommandParameter="EFGH"> </ToggleButton> <ToggleButton x:Name="IJKL" Content="IJKL" Height="30" Width="90" Command="{Binding FilterDataCommand}" CommandParameter="IJKL"> </ToggleButton> <ToggleButton x:Name="MNOP" Content="MNOP" Height="30" Width="90" Command="{Binding FilterDataCommand}" CommandParameter="MNOP"> </ToggleButton> </StackPanel> </Grid> </Window Dies ist die BackEnd C#, die Commands werden mit dem Konstruktor erstellt. internal class UserViewModel : NotifyPropertyChanged { public UserViewModel() { StartAppCommand = new RelayCommand((parameter) => StartApp()); FilterDataCommand = new RelayCommand((parameter) => { string x = (string) parameter; FilterData(x); }); } public ObservableCollection<FileInfo> DataList { get; set; } = new ObservableCollection<FileInfo>(); public ObservableCollection<FileInfo> FiltererdDataList { get; set; } = new ObservableCollection<FileInfo>(); public RelayCommand StartAppCommand { get; set; } public RelayCommand FilterDataCommand { get; set; } public RelayCommand RemoveDataCommand { get; set; } public void GetData() { } private void FilterData(string x) { ObservableCollection<FileInfo> NewList = new ObservableCollection<FileInfo>(); if (x == "ABCD") { NewList = FilterManager.AddData(DataList, FilterLibrary.FilterFile, x); } else { NewList = FilterManager.AddData(DataList, FilterLibrary.FilterNonFile, x); } foreach(FileInfo file in NewList) { FiltererdDataList.Add(file); } } private void RemoveData(string x) { if(x == "ABCD") { FilterManager.RemoveData(FiltererdDataList, FilterLibrary.FilterFile, x); } else { FilterManager.AddData(DataList, FilterLibrary.FilterNonFile, x); } } Ich möchte gerne erreichen, dass die RemoveData(string x) ausgelöst wird, wenn der ToggleButton wieder deaktiviert wird. Dadurch soll erreicht werden, das die vorher ausgesuchten Daten wieder aus der zweiten Liste entfernt werden (falls der Nutzer sich umentscheidet). Derzeit werden die Togglebutton wie normale Button gebunden, der Parameter ist wie in deinem Beispiel der benötigte String. Meine Idee dabei war es, mit einem Boolean zu erreichen, dass die Methoden bei True bzw. False ausgeführt werden. Wenn ich einen Togglebutton binde wie einen Normalen Button an ein Command binde, wofür besitzen dann Togglebuttons die Checked, IsChecked und UnChecked Property? Vielleicht verstehe ich ja auch nur den Gebrauch dieses Controls falsch. Da ich nicht beruflich mit der Materie unterwegs bin, wurschtel ich mich immer etwas semiprofessionell durch den Code - Jungel. Bearbeitet 15. Dezember 2023 von Gewinde Zitieren
eatenbaog Geschrieben 15. Dezember 2023 Geschrieben 15. Dezember 2023 (bearbeitet) Das sieht an sich schon mal ganz gut aus. So wie du das beschreibst, würde das auch mit den Boolean-Properties im ViewModel funktionieren. Ist wahrscheinlich nicht die schönste Lösung, da du für jeden Toggle eine Property benötigst und das if-else Konstrukt unschön wird, aber würde definitiv funktionieren. Dein Code müsste dann in etwa so aussehen: <Window.DataContext> <vm:UserViewModel/> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <ListBox x:Name="LeftBox" Grid.Column="0" Margin="5" ItemsSource="{Binding DataList}"> </ListBox> <ListBox x:Name="RightBox" Grid.Column="1" Margin="5" ItemsSource="{Binding FiltererdDataList}"> </ListBox> <StackPanel Grid.Column="2"> <Button x:Name="Start" Content="Start" Height="30" Width="90" Margin="5" Command="{Binding StartAppCommand}"> </Button> <ToggleButton x:Name="ABCD" Content="ABCD" Height="30" Width="90" IsChecked="{Binding ABCDToggleState}" Command="{Binding FilterDataCommand}" CommandParameter="ABCD"> </ToggleButton> <ToggleButton x:Name="EFGH" Content="EFGH" Height="30" Width="90" IsChecked="{Binding EFGHToggleState}" Command="{Binding FilterDataCommand}" CommandParameter="EFGH"> </ToggleButton> <ToggleButton x:Name="IJKL" Content="IJKL" Height="30" Width="90" IsChecked="{Binding IJKLToggleState}" Command="{Binding FilterDataCommand}" CommandParameter="IJKL"> </ToggleButton> <ToggleButton x:Name="MNOP" Content="MNOP" Height="30" Width="90" IsChecked="{Binding MNOPToggleState}" Command="{Binding FilterDataCommand}" CommandParameter="MNOP"> </ToggleButton> </StackPanel> </Grid> </Window> internal class UserViewModel : NotifyPropertyChanged { public UserViewModel() { StartAppCommand = new RelayCommand((parameter) => StartApp()); FilterDataCommand = new RelayCommand((parameter) => { string x = (string) parameter; FilterData(x); }); } public ObservableCollection<FileInfo> DataList { get; set; } = new ObservableCollection<FileInfo>(); public ObservableCollection<FileInfo> FiltererdDataList { get; set; } = new ObservableCollection<FileInfo>(); public bool ABCDToggleState { get; set; } public bool EFGHToggleState { get; set; } public bool IJKLToggleState { get; set; } public bool MNOPToggleState { get; set; } public RelayCommand StartAppCommand { get; set; } public RelayCommand FilterDataCommand { get; set; } public RelayCommand RemoveDataCommand { get; set; } public void GetData() { } private void FilterData(string x) { ObservableCollection<FileInfo> NewList = new ObservableCollection<FileInfo>(); if (x == "ABCD" && ABCDToggleState) { NewList = FilterManager.AddData(DataList, FilterLibrary.FilterFile, x); } else if (x == "ABCD" && !ABCDToggleState) { FilterManager.RemoveData(FiltererdDataList, FilterLibrary.FilterFile, x); } //Hier dann noch weitere if/else Zweige für die anderen 3-ToggleButtons hinzufügen foreach(FileInfo file in NewList) { FiltererdDataList.Add(file); } } Persönlich würde ich das wahrscheinlich über die https://github.com/microsoft/XamlBehaviorsWpf Library umsetzen (Import über NuGet). Hier kannst du dann bei verschiedenen Events des ToggleButtons verschiedene Funktionen ausführen. Somit könnte man sich das if-else-Konstrukt sparen und auch die neuen Boolean-Properties würden wegfallen. Beispiel, wenn du die Library unter dem Alias 'i' im XAML-File eingebunden hast: <ToggleButton> <i:Interaction.Triggers> <i:EventTrigger EventName="Unchecked"> <i:InvokeCommandAction Command="TODO: Uncheck-Command ausführen" CommandParameter="TogglePARAM"/> </i:EventTrigger> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="TODO: Check-Command ausführen" CommandParameter="TogglePARAM"/> </i:EventTrigger> </i:Interaction.Triggers> </ToggleButton> Eine dritte Option wäre, dass du den aktuellen State des ToggleButton mit ins VM übergibst. Dafür müsstest du aber einen MultiValueConverter schreiben, welcher dir zwei Eingaben in z.B. einen Tupel für das VM konvertiert. Praktisch der ToggleState und der ToggleControl-Name. Ist vermutlich bisschen aufwändiger. Hier wird das mit dem Multiconverter aber gut beschrieben: https://stackoverflow.com/questions/1350598/passing-two-command-parameters-using-a-wpf-binding Bearbeitet 15. Dezember 2023 von eatenbaog Library Link change + Typo + Clarification thrid option Zitieren
Gewinde Geschrieben 15. Dezember 2023 Autor Geschrieben 15. Dezember 2023 Hui, solch einen MultiValueConverter habe ich bis jetzt noch nie geschrieben. Ich werde am besten mal versuchen mit allen von dir angegebenen Varianten zu experimentieren, will dabei ja auch was lernen 🙂. Ich gehe fest davon aus das da noch sie eine oder andere Rückfrage von mir kommen wird. Mit Libraries arbeite ich in der Regel ungern, da dadurch oft der eigentlich notwendige Code verborgen bleibt. Wie z.B. diese MVVM Library von Microsoft, den Namen habe ich jetzt wieder vergessen. Wenn du schreibst, mit den Boolean wäre nicht die schönste Lösung, was ich verstehe wegen des doppelt und dreifachen Codes, wofür werden dann die bool Properties der Togglebutton genutzt? Sind die wirklich nur interessant wenn man im Codebehind mittels Event die Fensterfarbe o.ä. hell oder dunkel schalten möchte? Ich danke dir sehr für deine Hilfe. 👍 eatenbaog reagierte darauf 1 Zitieren
Tratos Geschrieben 18. Dezember 2023 Geschrieben 18. Dezember 2023 Also mein Beispiel mit den Pairs ist doch hier schon recht funktional, du kannst den Button entweder in der liste hinzufügen mit ADD oder entsprechend Löschen wenn er die Auswahl wieder rausnimmt.. Hier mal ein etwas komplexeres Beispiel: https://learn.microsoft.com/de-de/dotnet/api/system.collections.generic.list-1.add?view=net-8.0 Zitieren
eatenbaog Geschrieben 18. Dezember 2023 Geschrieben 18. Dezember 2023 Ich persönlich nutze ToggleButtons meist nur, um irgendwelche Properties meiner Entitäten auf True/False zu stellen. Alternativ so wie du schreibst, um irgendwelche Einstellungen im Bezug auf die UI auf True/False zu stellen. Eventuell würde einfach ein anderes Control für deinen Use-Case mehr Sinn machen, das kann ich dir aber vermutlich nicht beantworten, da ich deine Anwendung im Detail ja nicht kenne. Zitieren
Gewinde Geschrieben 27. Dezember 2023 Autor Geschrieben 27. Dezember 2023 Guten Morgen, ich hoffe ihr hattet alle ein schönes Weihnachtsfest. Also ich habe jetzt mal ein wenig probiert und getestet. Zumindest soweit ich das mit meinem gefährlichen Halbwissen hinbekomme. Bis jetzt bekomme ich meine Idee nur mit der bool Property im ViewModel umgesetzt. Bei der Multibindingvariante steige ich noch nicht wirklich hinter wo ich was binden kann. In meinem Fall meckert er mich voll das es kein Objekt gibt auf was ich binden könnte. Der Konverter ist im Window eingebunden Ebenso wie das ViewModel. <ToggleButton x:Name="tg_button" Height="30" Width="90" Content="FPLO" Command="{Binding ToggleCommand}"> <ToggleButton.CommandParameter> <MultiBinding Converter="{StaticResource Conv}"> <Binding Path="IsChecked" ElementName="tg_button"/> <Binding Source="FPLO"/> </MultiBinding> </ToggleButton.CommandParameter> </ToggleButton> Der Code ist nur eine von sehr vielen Varianten, alle hier zu posten würde wahrscheinlich das Internet in die Knie zwingen. Eigentlich egal wo ich etwas versuche zu binden, WPF heult aus vollen Rohren das es nicht damit klar kommt. In dem Video von Entwickler 42 wird ein Beispiel dargestellt, wo es um DatePicker u.s.w. geht. Dort funktioniert es natürlich, allerdings enorm Kompliziert. Leider wüsste ich nicht wie ich die Idee vom Entickler 42 in meinem Fall umsetzen könnte, die Variante von Stackoverflow schlägt ebenso fehl, da er mir sagt es gibt keine Objekte. Eventuell wäre es doch besser die unübersichtliche Variante oder aber einfach ein anderes Control zu verwenden, was ich ungerne machen möchte, da es für mich bedeutet 1:0 für die WPF. Vielleicht sollte ich mich auch mal nach einem anderen Tool für die UI bemühen, da ich jetzt schon öffters auf solche Probleme treffe. Eigentlich soll (so habe ich es gelesen) die WPF ein starkes Tool sein, bei der Komplexität zweifle ich da allerdings offtmals dran. Zitieren
Gewinde Geschrieben 27. Dezember 2023 Autor Geschrieben 27. Dezember 2023 @Tratos ich danke dir sehr für deinen Input, allerdings komme ich mit den Angaben von dir nicht ganz klar. Wahrscheinlich verstehe ich diese nur nicht wirklich. Ich möchte keine Liste mit Togglebutton erstellen, sondern eine Liste mit verschiedenen Togglebutton bearbeiten und dann in einer anderen Liste ausgeben. Zitieren
Empfohlene Beiträge
Dein Kommentar
Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.