Zum Inhalt springen

ToggleButton und MVVM


Gewinde

Empfohlene Beiträge

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

Link zu diesem Kommentar
Auf anderen Seiten teilen

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 von Tratos
Link zu diesem Kommentar
Auf anderen Seiten teilen

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 

Link zu diesem Kommentar
Auf anderen Seiten teilen

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?

Link zu diesem Kommentar
Auf anderen Seiten teilen

 

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);
        }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

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 von Gewinde
Link zu diesem Kommentar
Auf anderen Seiten teilen

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 von eatenbaog
Library Link change + Typo + Clarification thrid option
Link zu diesem Kommentar
Auf anderen Seiten teilen

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. 👍

Link zu diesem Kommentar
Auf anderen Seiten teilen

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

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

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.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 Wochen später...

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. :D

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.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@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.

Link zu diesem Kommentar
Auf anderen Seiten teilen

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.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...