Binden einer Combobox mit Checkboxen gefüllt an eine Klasse

  • WPF

Es gibt 49 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Hallo

    kafffee schrieb:

    Warum hast du für die Klasse Genres im Model dann nochmal ne Klasse GenreViewModel im ViewModel reingemacht?

    Als ersten Grund erstmal da uns das MVVM Pattern (Model-View-ViewModel) dies ja schon so vorgibt. OK, verstehe das dies kein nachvollziehbarer Grund ist. "Ja, nur weil das so in einem Pattern steht ist kein Grund". Kann ich verstehen. Warum das Pattern dies so vorgibt, oder besser gesagt warum es empfohlen wird ist aber interessant.

    Im Normfall definiert man ein Model oder besser gesagt eine Modelklasse als sogenannte POCO Klasse. PlainOldClassObjects
    Diese Klassen sind simple "dumme" Klassen. Haben also keinerlei Logik, keine Prozeduren usw.
    Sie haben lediglich Eigenschaften (Properties) und definieren in einer CRUD Anwendung auch gleichzeitig die Datenstruktur. Also wie Daten gespeichert werden sollen. Evtl. sind die Eigenschaften noch mit DataAnnotations versehen, aber dann wars das schon. Damit implementieren diese Klassen auch kein INotifipropertyChanged welches wir aber für den Bindingmechanismus in der WPF benötigen. Somit können diese Klassen nicht direkt zum Binden verwendet werden. (Oder nicht gut, denn über Änderungen wird die View nicht Benachrichtigt. Sollte ja bekannt sein.)

    OK, soweit ist das klar. Jetzt könnten wir aber ja hergehen und sagen wir implementieren in die Modelklasse einfach iNotifypropertyChanged. Dann können wir die Klasse ja nun super in der View verwenden und alles ist gut.
    Jein. Was mache ich mit Commands? Wo packe ich nun Timer hin? Wo eigene Events? Alles dort rein? Wie schließe ich das alles beim speichern aus? Es wird unübersichtlich.

    Das geht sicher eine Zeit lang gut. Plötzlich kommt der Punkt wo ich die Daten mal evtl. nicht so in der View anzeigen will wie ich diese Speichern will. Ups. Was mach ich nun. Füge ich nun eine Eigenschaft in die Klasse ein wird mir diese auch so persistiert wenn ich z.b. Serialisiere oder mittels EntityFramework oder anderen O/R Mappern in eine DB schreibe. Doof. Gut, dann biegen wir irgendwas um. Russisch. Ne, wir arbeiten sauber. Gut, also eine Klasse drumrum gebaut (was ja ein ViewModel in diesem Fall ist) aber das habe ich dann nur für diesen Fall. OK, muss ich mir merken das ich das in diesem Fall so gemacht habe.

    Ich mache das lieber mit jeder Klasse von Haus aus, denn dann habe ich eine klare Designlinie des Codes, ich weis wie mein Code aufgebaut ist weil ich es immer und überall gleich mache.
    Ich habe für jede Modelklasse einfach auch eine ViewModel-Klasse. Das weis ich in diesem Moment. Dort kann ich mir die Eigenschaften so machen wie ichs im View anzeigen will, ich habe dort meine Command die an die View gebunden sind, evtl. Events, Timer usw.

    Jetzt kann man sagen: "Im normalfall zeige ich im View ja eh genau die Daten an die ich auch speichere"
    Ne! Ich habe es immer wieder das ich das nicht mache. Ein gutes Beispiel ist z.b. eine Passworteingabe. Im View gebe ich das Passwort in ein Feld ein, das ist im Grunde ein normaler Text. Speichern will ichs aber Verschlüsselt.
    Also habe ich im ViewModel eine Logik die mir das macht und in die Eigenschaft des Model-Objekts dann Verschlüsselt packt bevor gespeichert wird.
    Oder ich habe in meinen Daten den Zeitpunkt der letzten Änderung eines Datensatzes als Timestamp. Anzeigen möchte ich aber Datum/Uhrzeit.
    Es gibt viele Szenarien.

    Ein weiterer Punkt welcher mir selbst immer wieder begegnet (aber das muss nicht auf jeden Zutreffen) ist das man oft Projekte hat welche gewisse Teile teilen müssen. Und das wäre meißt das Model.
    Ein Beispiel wäre hier wenn ich eine Webanwendung für die Enduser hätte aber z.b. die Statistik oder die Administration als WPF Anwendung. Oder zwei WPF Anwendungen Eine für die USer, eine für die Admins oder die Chefs. Oder ich habe ein Webservice auf welches z.b. eine Webanwendung und eine Handy-App zugreifen. Diese Anwendungen müssen wenn Sie Daten von Webservice abrufen das Model kennen um etwas mit den Daten anfangen zu können (zumindest wirds damit um ein vielfaches einfacher).
    Wenn im Model dann Abhängigkeiten wie Commands oder iregendwelche Implementierungen vorhanden sind wird oft schwierig. Unter Xamarin gibts keinen CommandManager.

    Und wenn man noch länger darüber nachdenkt gibts vermutlich noch ein paar Gründe.

    Es ist auch klar das es sein kann das keine dieser Gründe für einen zutreffen. Dann könnte man es probieren. Die Frage ist: Riskieren?
    Wenn ich weis ich bin damit flexibler dann mach ich es. Es ist ein wenig mehr Tipparbeit. Richtig. Aber mir ist es das wert das ich weis ich bin auf der sicheren Seite und bin flexibel.

    In deinem Fall (in diesem Projekt) speicherst du Gerne ja nichtmal. Also nicht als Auflistung oder sowas. Das habe ich gesehen. Im Grunde würde es diese Klasse überhaupt nicht benötigen da du in deiner Übergeordneten Klasse sowieso als simplen String speicherst. Aber um eben genau dieses Prinzip zu veranschaulichen habe ich es gleich so gebaut. Und das zeigt mir das du dir den Code auch wirklich angesehen und versucht hast zu verstehen. Dafür erstmal meinen Respekt. Und das meine ich so wie ich es schreibe. Nicht viele gehen den Code genau durch und versuchen alles zu verinnerlichen. Hut ab. So muss das, wenn man was lernen will.

    So, jetzt habe ich eh wieder mal einen Roman geschrieben und bin wieder mal viel zu weit gegangen. Sorry. Hoffe es hilft trotzdem.
    Wenn du noch Fragen hast, tu dir keinen Zwang an.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Nofear23m schrieb:

    So muss das, wenn man was lernen will.


    Tut auch mal gut sowas zu hören. ^^

    Bin gerade dabei, das zu programmieren, dass wenn man einen Interpreten in der ersten Liste anklickt, dass dann alle Alben mit genau diesem Interpreten angezeigt werden.

    Habe jetzt Folgendes:


    XML-Quellcode

    1. <ListBox Name="lstInterpreten" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding AnzuzeigendeInterpreten}" DisplayMemberPath="Interpret" SelectedItem="{Binding AusgewählterInterpret, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Single"/>


    Und die dazugehörige Klasse:

    VB.NET-Quellcode

    1. Public Property AusgewählterInterpret As String
    2. Get
    3. Return _AusgewählterInterpret
    4. End Get
    5. Set(value As String)
    6. If String.IsNullOrEmpty(value) = False Then
    7. _AusgewählterInterpret = value
    8. RaisePropertyChanged()
    9. RefreshView() 'neu filtern lassen
    10. End If
    11. End Set
    12. End Property


    Wenn ich nun aber einen Interpreten anklicke, kommt es zu folgendem Fehler in der Zeile 45 der ViewModelBase.vb:
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop)) >>

    System.StackOverflowException
    HResult=0x800703E9
    Quelle = <Die Ausnahmequelle kann nicht ausgewertet werden.>
    Stapelüberwachung:
    <Die Ausnahmestapelüberwachung kann nicht ausgewertet werden.>

    Mein Filter sieht jetzt so aus:

    VB.NET-Quellcode

    1. Private Function AnzuzeigendeAlben_Filter(obj As Object) As Boolean
    2. Dim Objekt As MP3FileInfo = DirectCast(obj, MP3FileInfo)
    3. Dim IstErlaubt = Not Objekt.Stream
    4. If Not IstErlaubt Then Return IstErlaubt
    5. If Not String.IsNullOrEmpty(TextFilter) Then
    6. If AusgewählterInterpret = "" Then 'ist ein Listbox Item in der lstInterpreten angeklickt??
    7. IstErlaubt = Objekt.Interpret.ToLower.Contains(TextFilter) OrElse Objekt.Album.ToLower.Contains(TextFilter)
    8. If Not IstErlaubt Then Return IstErlaubt
    9. Else
    10. IstErlaubt = Objekt.Interpret = AusgewählterInterpret 'das hier soll filtern, wenn die Eigenschaft "Interpret" des ListBox Items der ListBox lstAlben gleich der des angeklickten Items in der lstInterpreten ist
    11. If Not IstErlaubt Then Return IstErlaubt
    12. End If
    13. End If
    14. For Each selGenre In AnzuzeigendeGenres.Where(Function(g) Not g.IstSelektiert)
    15. If Objekt.Genre = selGenre.Genre Then Return False
    16. Next
    17. Return IstErlaubt
    18. End Function


    Wie man sieht habe ich mich dazu entschieden, für jede Listbox einen eigenen Filter zu bauen, weil mir das sonst zu viele Variablen in einer Methode gewesen wären...

    Leider weiss ich mit diesem Fehler in diesem Kontext nichts anzufangen. Kann mir jemand den entscheidenden Hinweis geben? Wahrscheinlich habt ihr aber noch Rückfragen oder braucht mehr Code, also gerne Rückfragen stellen :)

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „kafffee“ ()

    kafffee schrieb:

    System.StackOverflowException

    tritt eigentlich nur bei entarteten Rekursionen auf.
    Also irgendwo hast du da über Events und weitere Stufen etwas hingebracht, wo Methoden einander im Kreis aufrufen.
    Haltepunkt setzen und durchsteppen, dabei den Callstack beobachten.
    Wenn letzterer eine Methode aufzeigt, die weiter unten schonmal vorkam, dann hat sich der Kreis geschlossen.

    kafffee schrieb:

    Und die dazugehörige Klasse:

    Ist zwar nur eine Eigenschaft aber egal. Das ist so nicht korrekt. ​AnzuzeigendeInterpreten ist vom Typ ​List(Of MP3File) somit ist ein SelectedItem einer Listbox von Typ MP3File und nicht vom Typ String.
    Da fängt es ja mal an. Ja, die WPF kann dir das über die DefaultConverter evtl. Konvertieren (was ich in diesem Fall garnicht glaube).
    Ändere also mal die Eigenschaft.

    kafffee schrieb:

    Beim Filtern sollte ich nicht AusgewählterInterpret verwenden, sondern _AusgewählterInterpret

    Das ist völlig egal. ​AusgewählterInterpret gibt ja schlussendlich ja auch nur ​_AusgewählterInterpret zurück. Gehüpft wie gesprungen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Wenn es dann noch nicht klappt setze einen Haltepunkt im Setter, dann siehst du ja wie und wo die Endlosschleife passiert.
    Ich nehme aber stark an das wenn du beim selektieren einen Filter setzt und sich die Ansicht aktualisiert das hier das Binding greift und der SelektedItem abermals zurückgeschrieben wird was wieder ein Filtern bewirkt, was dann wieder............. Du siehst, das löst sowas aus. :S
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    @Nofear23m

    Jep sowas in der Art hab ich mir auch schon gedacht.

    Das werd ich tun und wie ErfinderDesRades empfohlen hat, den Callstack dabei anglupschen...

    Mach ich dann aber morgen, oder heut nacht wenn ich wiedermal nicht schlafen kann...

    @ErfinderDesRades
    @Nofear23m

    Edit:
    Also das tritt tatsächlich im Setter von AusgewählterInterpret auf. Siehe Screenshot. Leider seh ich in der Aufrufliste keinen doppelten Eintrag. Oder kommt sich da etwa AusgewählterInterpret mit AusgewähltemAlbum in die Quere? Die gleichen sich nämlich. Ich kann das RefresView ja nun nicht einfach rausnehmen, dann passiert natürlich gar nichts. Was auch komisch ist: Manchmal tritt der Fehler auch nicht in der ViewModelBase.vb auf sondern in der RefreshView() in Zeile 1 bei Anzuzeigende Interpreten.Refresh(). Ich hab noch nicht herausgefunden warum das mal so mal so ist...

    Was könnt ihr mir also vorschlagen?

    Meine RefreshView():

    VB.NET-Quellcode

    1. Private Sub RefreshView()
    2. AnzuzeigendeInterpreten.Refresh()
    3. AnzuzeigendeAlben.Refresh()
    4. AnzuzeigendeMusiktitel.Refresh()
    5. AnzuzeigendeRadiostreams.Refresh()
    6. End Sub



    Edit: Ich probier mal in der AusgewählterInterpret statt RefreshView bloss AnzuzeigendeAlben. Refresh...

    @Nofear23m
    @ErfinderDesRades

    Edit: Yep das wars :thumbsup:

    Jetzt hab ich nur noch ein klitzekleines Problem mit der Filterlogik aber das krieg ich auch noch hin...

    Und das:

    Ich möchte dass wenn die Textbox den Fokus bekommt, der SelectedIndex der Listboxen auf Nothing gesetzt wird und die Textbox.Text = "" gesetzt wird.

    Nun könnte ich ja locker ein Eventprozedur erstellen und dann die gewollten Aktionen ausführen. Nur ist das wahrscheinlich nicht wieder im Sinne des MVVM...

    Wie gehe ich da am besten an die Sache ran? Ich nehme an mit einem Command?
    Bilder
    • screenshot-stackoverflow.jpg

      144,43 kB, 836×535, 20 mal angesehen

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „kafffee“ ()

    kafffee schrieb:

    Ich möchte dass wenn die Textbox den Fokus bekommt, der SelectedIndex der Listboxen auf Nothing gesetzt wird und die Textbox.Text = "" gesetzt wird.


    Du könntest der ListView/DataGrid einen Namen geben und im ViewModel durch FindName("NAME") dein Objekt finden und dann SelectedItem(s) setzen. Wenn es eine ListView ist und IsSynchronizedWithView = true ist, kannst du das darüber steuern. Ob das im Sinne des MVVM ist, weiß ich nicht, aber möglich wäre es.
    @ErfinderDesRades

    Ich möchte dass wenn die Textbox den Fokus bekommt, der SelectedIndex der Listboxen auf Nothing gesetzt wird und die Textbox.Text = "" gesetzt wird.

    Properties für den Text und den Selectedindex sind bereits vorhanden...

    Also Problem ist eher das GotFocus Event, wie man das mit Mvvm macht.
    Hallo

    Ich weis nicht wie lange du noch versucht "gegen" die WPF zu arbeiten und ohne Binding irgendwelche Workarounds zu suchen.

    kafffee schrieb:

    Ich möchte dass wenn die Textbox den Fokus bekommt, der SelectedIndex der Listboxen auf Nothing gesetzt wird

    Tja, eine Eigenschaft im ViewModel vom Typ Boolean "IsMyTextBoxFocused" und darauf wird die Eigenschaft "IsFocused" der Textbox gebunden.
    Im Setter der VM-Eigenschaft kannst dann SelectedItem (Nicht SelectedIndex) auf Nothing setzen. Denn diese Eigenschaft ist ja nicht vom Typ Integer. Musst natürlich im View auch gebunden werden.Ist das kleine 1x1 des Binding.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    ErfinderDesRades schrieb:

    Wenn nun jemand was in die Textbox schreibt

    Was hat das eine mit dem anderen zu tun. Was soll dann passieren? Nichts. Der Setter wird wenn IsFocused = true wird 1x aufgerufen und dann wieder beim verlassen der TextBox - dann allerdings mit dem Wert false.
    Also wo genau siehst du nun ein Problem?

    Grüße
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Korrigiert mich, aber es ging dem TE doch garnicht um derartiges.
    Wenn ich mir den ersten Post ansehe geht es hier um eine Art Such/Filterfunktion und wenn er in die TextBox klickt soll die Selektion eine ListBox zurückgesetzt werden. So verstehe ich das zumindest.

    wie gesagt, korrigiert mich, evtl. habe ich etwas wichtiges überlesen. ?(
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    @Nofear23m: Meine Antwort bezog sich eher auf das hier:

    kafffee schrieb:

    Und das:

    Ich möchte dass wenn die Textbox den Fokus bekommt, der SelectedIndex der Listboxen auf Nothing gesetzt wird und die Textbox.Text = "" gesetzt wird.

    Nun könnte ich ja locker ein Eventprozedur erstellen und dann die gewollten Aktionen ausführen. Nur ist das wahrscheinlich nicht wieder im Sinne des MVVM...

    Wie gehe ich da am besten an die Sache ran? Ich nehme an mit einem Command?
    OK, auch genau das geht mit meinem Lösungsansatz!!

    Also?
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##