Databinding, wie denn jetzt?!

  • WPF

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von LaMiy.

    Databinding, wie denn jetzt?!

    Ich wollte mir nun endlich mal WPF ein bisschen mehr aneignen und bin beim DataBinding hängen geblieben.
    Googleergebnisse gab es da sehr viele, aber auch beinahe so viele verschiedene Ansätze.
    Den Grundgedanken habe ich verstanden. wpftutorial.net/DataBindingOverview.html
    Hier habe ich auch eine Klasse gefunden, deren Nutzung recht schick aussieht. codeproject.com/Articles/23487…perty-Dependencies-Part-I
    Hat aber nicht so ganz geklappt. (Finde die Klasse an sich und die Nutzung aber wirklich sehr schick, mehr dazu weiter unten)

    Nun hab ich so viele Möglichkeiten gefunden. Doch welche ist denn jetzt richtig/smart wenn ich einfach eine Property einer Klasse an ein Control binden will?
    (z.B den Namen eines Tiers an ein Label)

    Hier nochmal die Klasse, die ich auf codeproject gefunden habe.
    Aufruf.

    C#-Quellcode

    1. private readonly Property<string> test = new Property<string>();
    2. public Property<string> Test
    3. {
    4. get
    5. {
    6. return test;
    7. }
    8. }

    XML-Quellcode

    1. <Label Content="{Binding Path=Test.Value}" ...

    C#-Quellcode

    1. /// <summary>
    2. /// Stellt eine Klasse zum Verwalten von Klassenattributen dar
    3. /// </summary>
    4. /// <typeparam name="T"></typeparam>
    5. public class Property<T> : INotifyPropertyChanged
    6. {
    7. private T value;
    8. private bool changed = false;
    9. public Property() { }
    10. public Property(T value)
    11. {
    12. this.value = value;
    13. }
    14. public T Value { get { return value; }
    15. set
    16. {
    17. this.value = value;
    18. NotifyPropertyChanged("Value");
    19. Changed = true;
    20. }
    21. }
    22. public bool Changed { get { return changed; }
    23. set
    24. {
    25. changed = value;
    26. NotifyPropertyChanged("Changed");
    27. }
    28. }
    29. public void Commit() { Changed = false; }
    30. private void NotifyPropertyChanged(string propertyName)
    31. {
    32. if (PropertyChanged != null)
    33. PropertyChanged(this,
    34. new PropertyChangedEventArgs(propertyName));
    35. }
    36. public event PropertyChangedEventHandler PropertyChanged;
    37. }
    Wenn Du den DataContext gesetzt hast, nämlich auf das entsprechende ViewModel, zum Beispiel so bei mir:

    XML-Quellcode

    1. DataContext="{x:Static viewmodel:MainViewModel.MainStatic}"


    dann machste einfach das Binding direkt an die Eigenschaft im Designer:

    XML-Quellcode

    1. "{Binding Text}"


    oder wie auch immer das bei Dir heißt.
    Ich hoffe ich habe jetzt verstanden, was das Problem ist :D
    Wenn nicht, erkläre es bitte nochmal.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Also ich muss ehrlich zugeben, dass ich das immer anders mache, auf die schwierige Art und Weise ^^
    Daher weiß ich jetzt nicht inwiefern das window bei Dir was zu sagen hat. Der Mode passt so, insofern Du das so willst.

    Ich setze als DataContext halt immer das entsprechende ViewModel, allerdings ist das bei mir komplizierter. Wenn Du willst, zeige ich es Dir.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Also ich fand es mit dieser Klasse public class Property<T> : INotifyPropertyChanged echt schick, weil man dann fast keinen Code im Model selber braucht.
    (Nur halt die Property) Ich vermute aber, dass man das ViewModel trotzdem so wie du setzten kann.
    Aber zeig mal ruhig wenn du gerade Zeit/Lust hast. Ist ja nicht schlecht zu sehen wie man es sonst noch machen kann.
    Ok. Tut mir Leid, habe jetzt nur das in VB da, aber ich gehe mal davon aus, dass Du es trotzdem verstehst ;)

    Also ich habe folgende Struktur:

    ViewModelView (Die GUI)

    ViewModelBase.vb-
    MainViewModel.vbMainWindow.xaml

    Also das MainViewModel baut auf dem ViewModelBase auf und das MainWindow benutzt in diesem Fall das für es ausgerichtete MainViewModel.
    Die ViewModelBase baut wiederrum auf eine Klasse PropertyChangedBase.vb auf, die sieht so aus: (Ist von thefiloe aus dem Forum hier)

    PropertyChangedBase.vb

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Linq.Expressions
    3. Imports System.Reflection
    4. Imports System.Runtime.CompilerServices
    5. <Serializable> _
    6. Public Class PropertyChangedBase
    7. Implements INotifyPropertyChanged
    8. Public Event PropertyChanged As PropertyChangedEventHandler _
    9. Implements INotifyPropertyChanged.PropertyChanged
    10. Public Function SetProperty(Of T)(value As T, ByRef field As T, [property] As Expression(Of Func(Of Object))) As Boolean
    11. Return SetProperty(value, field, GetPropertyName([property]))
    12. End Function
    13. Public Function SetProperty(Of T)(value As T, ByRef field As T, <CallerMemberName> Optional propertyName As String = Nothing) As Boolean
    14. If field Is Nothing OrElse Not field.Equals(value) Then
    15. field = value
    16. OnPropertyChanged(propertyName)
    17. Return True
    18. End If
    19. Return False
    20. End Function
    21. Public Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
    22. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    23. End Sub
    24. Public Sub OnPropertyChanged([property] As Expression(Of Func(Of Object)))
    25. OnPropertyChanged(GetPropertyName([property]))
    26. End Sub
    27. Public Function GetPropertyName([property] As Expression(Of Func(Of Object))) As String
    28. Dim lambda = TryCast([property], LambdaExpression)
    29. Dim memberExpression As MemberExpression
    30. If TypeOf lambda.Body Is UnaryExpression Then
    31. Dim unaryExpression = TryCast(lambda.Body, UnaryExpression)
    32. memberExpression = TryCast(unaryExpression.Operand, MemberExpression)
    33. Else
    34. memberExpression = TryCast(lambda.Body, MemberExpression)
    35. End If
    36. If memberExpression IsNot Nothing Then
    37. Dim constantExpression = TryCast(memberExpression.Expression, ConstantExpression)
    38. Dim propertyInfo = TryCast(memberExpression.Member, PropertyInfo)
    39. If propertyInfo IsNot Nothing Then
    40. Return propertyInfo.Name
    41. End If
    42. End If
    43. Return [String].Empty
    44. End Function
    45. End Class



    So, dann die ViewModelBase.vb:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class ViewModelBase
    2. Inherits PropertyChangedBase
    3. Public ReadOnly Property Main As MainViewModel
    4. Get
    5. Return MainViewModel.MainStatic
    6. End Get
    7. End Property
    8. End Class



    und schließlich das MainViewModel, wo Du die ganzen Properties für deine GUI-Elemente findest:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class MainViewModel
    2. Inherits ViewModelBase
    3. Private Shared _instance As MainViewModel
    4. Public Shared ReadOnly Property MainStatic As MainViewModel
    5. Get
    6. If _instance Is Nothing Then
    7. _instance = New MainViewModel()
    8. End If
    9. Return _instance
    10. End Get
    11. End Property
    12. Dim _text As String = "Test"
    13. Public Property Text As String
    14. Get
    15. Return _text
    16. End Get
    17. Set(value As String)
    18. SetProperty(value, _text)
    19. End Set
    20. End Property
    21. End Class



    Da siehst Du jetzt die Property "Text". Die binden wir jetzt einfach an einen Buttontext, nachdem wir für das MainWindow den DataContext setzen. Das ist der vom MainViewModel, das heißt, jedes Window hat ein eigenes und das musst Du halt verwalten. Zum Beispiel:

    XML-Quellcode

    1. <!-- Das MainWindow-Eigenschaftenzeugs -->
    2. DataContext="{x:Static viewmodel:MainViewModel.MainStatic}"


    XML-Quellcode

    1. <Button Content="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>


    In dem Fall habe ich noch den UpdateSourceTrigger gesetzt, da ich bei Änderung die Property ändern wollte (stammt nämlich ursprünglich aus nem Textfeld :P)
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    ich finde eine Klasse namens "Property" sehr eigentümlich. Properties sind Properties, keine Klassen.
    Also man codet sein Viewmodel, und das hat Properties, aber eine Klasse, die eine Property ist - naja, hab ich wohl das Konzept nicht verstanden.
    Einem Einsteiger würde ich jdfs. eher nicht empfehlen mit solchen Exoten einzusteigen.

    Standard-Vorgehensweise ist, dass Viewmodel-Klassen von ViewmodelBase erben, da kann man neuerdings (2013) sogar mit Attributen steuern, welcher Property-Setter das PropertyChanged-Event feuert.

    Ich selbst hab nur 2010, da hat man die Viewmodelbase-Klasse noch selbst schreiben müssen - vlt. guggst du WpfTreeview-Tut, das ist vom Model her noch recht überschaubar.
    auch gugge 4ViewsWpf - das ist variantenreicher.
    Und gugge Video zum Binding-Picking im Xaml-Editor - weil viele Progger wissen garnet, dasses das gibt (oder schätzen es gering)
    @Trade Sieht auch sehr gut aus. Werde ich mir nochmal genauer anschauen wenn ich das Grundgerüst habe.
    @ErfinderDesRades Das hört sich auch gut an.
    Ich kenne MVC (Model View Controller) aus PHP
    Nur zum Verstehen. Das Model ist dazu da die Daten zu hoeln/speichern. Woraus rufe ich das Model auf? Aus dem XAML Kontext?
    Man hat ja jetzt ein Model, dass INotifyPropertyChanged implementiert. Da hat man die Datenverwaltung + Properties drin.
    Sowas z.B

    VB.NET-Quellcode

    1. Public Property Test As String
    2. Get
    3. Return _test
    4. End Get
    5. Set(ByVal value As String)
    6. _test = value
    7. If value.NotNull Then RaisePropChanged("Test")
    8. End Set
    9. End Property

    Wie spreche ich das jetzt bei XAML an? Und wie spreche ich im DataContext explicit dieses Model an?
    Das Model beinhaltet die Daten, das View soll Daten anzeigen und das Viewmodel ist quasi die Schnittstelle von beidem, also das Viewmodel kennt das Model und dient als Datacontext des Views.

    Wenn der Datacontext also vorhanden ist, kannst du die an die Properties dieses Viewmodels binden, bei deinem Beispiel von oben zB. so: <TextBlock Text="{Binding Path=Test}" />
    Genau, aber beachte dennoch den DataContext, den musst Du dann auf das FileViewModel setzen.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Okay, ich hab es mal probiert. Ich fand das mit der Property<T> doch sehr schick. Ist ja im Prinzip das was Erfinder gesagt hatte. Nur etwas ausgelagert. Ich habe den Namen aber mal in NotifyProperty geändert.
    Das ist mein ViewModel.

    C#-Quellcode

    1. public class Tester
    2. {
    3. private readonly INotifyProperty<string> test = new INotifyProperty<string>("Das ist ein Test");
    4. public INotifyProperty<string> Test
    5. {
    6. get
    7. {
    8. return test;
    9. }
    10. }
    11. }

    Ansprechen tu ich das so.

    XML-Quellcode

    1. <Border BorderThickness="1" Background="#FF2E2D2D" BorderBrush="Black" CornerRadius="0" Opacity="0.9" RenderTransformOrigin="0.509,0.517">
    2. <Label DataContext="{Binding Path=Tester}" Content="{Binding Path=Test.Value}" HorizontalAlignment="Left" Height="24.8" Margin="-1,-1,0,0" VerticalAlignment="Top" Width="93.6" Foreground="#FFFBFBFB" />
    3. </Border>


    Klappt aber leider nicht. Weiß jemand wo der Fehler liegt. Habe zwar gerade mal nach DataContext gesucht, aber da gibt es so viele unterschiedliche Antworten (Irgendwas mit StaticRessource oder Source z.B)

    LaMiy schrieb:

    Ich kenne MVC
    MVVM ist was ganz anneres.
    Ich fasse MVVM auch total lax auf, indem ich meist Model und Viewmodel zusammenschmeiße.
    Also ich binde auch an Klassen, die eiglich dem Model zuzuordnen wären, und spare mir das Heckmeck mitte Viewmodel-Schicht - sofern das problemlos möglich ist.

    MVVM-Puristen schreiben um jede Model-Klasse eine ModelView-Klasse, und das führt bei Listen zu Problemen, denn dann hast du eine Liste mit Models und eine Liste mit Viewmodels, und die musst du dann auch synchron halten, also wenn einer ein Viewmodel löscht, dann muss das entsprechende Model auch aus der Modelliste rausfliegen, und bei Zufügungen ähnliches Theater - und bei einfachen Viewmodels ohne jeden Nutzwert.

    Interessanterweise war das Zusammenschmeißen zB beim Treeview-Tut nicht möglich - da habe ich eine richtige Viewmodelschicht eingezogen zwischen den Models (FileInfo, DirectoryInfo) und dem Xaml. Also das ist auch ein gutes (v.a. weil sinnvolles) Sample für "richtiges" MVVM.

    Hingegen zB bei WPF-TicTacToe ist keine Model-Schicht erkennbar - ist eiglich alles nur Viewmodel.

    (Ups - sehe grad, du fragst ja wg. eines Problems. Aber zu meiner Entschuldigung fürs Ignorieren der Frage kann ich anführen, dass man auf "Klappt nicht" eh nicht sinnvoll eingehen kann)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    ​kann ich anführen, dass man auf "Klappt nicht" eh nicht sinnvoll eingehen kann)
    Normalerweise ja, aber was soll ich denn hier näher beschreiben? Es kommt kein Fehler, es wird mir aber auch kein Text angezeigt. (=> klappt halt nicht) Der benutze Code steht oben. Es muss am BindingContext liegen. Und meine Frage diesbezüglich war wie man das genau macht, denn

    LaMiy schrieb:

    ​'habe zwar gerade mal nach DataContext gesucht, aber da gibt es so viele unterschiedliche Antworten (Irgendwas mit StaticRessource oder Source z.B)

    Hast du vielleicht eine Lösung?
    In deinem Projekt finde ich dazu nämlich beim FolderBrowser nichts.
    Das ist doch eine wichtige Beschreibung: Kein Absturz, aber auch keine Funktion. Und ist typisch für Fails beim Setzen von Bindings.
    Deshalb Bindings mit dem PropertyFenster setzen, wie im Folderbrowser-Video gezeigt.
    Werden die Bindings nicht angeboten, so ist was faul mittm DataContext, meist mittm DataContext des Windows.
    Ich hab dir mal eine ganz gekürzte Rohversion gebastelt, die nur das Binding verdeutlichen soll, da ist nichts mit ViewModelBase und INotifyPropertyChanged.
    Folgendes gibt es zu beachten:

    - das ViewModel wird in den App-Resources (global) einmal instanziert. Beachte, dass zunächst der Namespace deiner Anwendung dort eingebunden werden muss, um deine Klassen ansprechen zu können (geht schnell über intellisense, ich hab den Namespace this genannt)
    - dem Objekt noch über x:Key einen Bezeichner geben, über den du das Objekt dann ansprechen kannst
    - das Viewmodel habe ich in den Window-DataContext eingebunden, dadurch ist es im ganzen View verfügbar
    - beachte die Syntax, als Quelle der Bindung habe ich {StaticResource xKey} angegeben
    - da das Viewmodel nun überall bekannt ist, kann man direkt per Path an die Properties binden, siehe TextBlock


    Siehe Anhang.
    Dateien

    ErfinderDesRades schrieb:

    Werden die Bindings nicht angeboten, so ist was faul mittm DataContext, meist mittm DataContext des Windows.
    Ja, genau. Da stimmt eben etwas nicht und ich will wissen was. :thumbsup: Zum einfacheren Testen habe ich das ViewModel jetzt mal "normal" gemacht.

    C#-Quellcode

    1. public class Tester : INotifyPropertyChanged
    2. {
    3. private string _test = "Test";
    4. public string Test
    5. {
    6. get
    7. {
    8. Debug.Print("Abgerufen");
    9. return _test;
    10. }
    11. set
    12. {
    13. _test = value;
    14. PropertyChanged(this, new PropertyChangedEventArgs("Test"));
    15. }
    16. }
    17. public event PropertyChangedEventHandler PropertyChanged;
    18. }

    So, jetzt habe ich im XAML Editor meines Hauptwindows noch nichts einstellt. Was muss ich jetzt alles beachten wenn ich den Content meines Labels an die Property Test des Viewmodels Tester binden will? (So jetzt die Frage einfach mal direkt :) )
    im Xaml muss der DataContext des Windows gesetzt sein. Wird das im Folderbrowser Tut nicht gezeigt?
    Ok - gibt da viele Möglichkeiten:

    hier ist an eine StaticResource im App.Xaml gebunden:

    XML-Quellcode

    1. <Window x:Class="MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. Title="MainWindow" Height="350" Width="525"
    5. xmlns:hlp="clr-namespace:System.Windows.Controls;assembly=HelpersSmallEd"
    6. xmlns:my="clr-namespace:FolderBrowserTester"
    7. DataContext="{StaticResource MainModel}">


    hier wird der DataContext direkt im Xaml instanziert (entspricht Sub New())

    XML-Quellcode

    1. <Window
    2. x:Class="DirectoryBrowser"
    3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    5. Title="DirectoryBrowser" Height="500 " Width="500"
    6. xmlns:my="clr-namespace:FolderBrowser"
    7. FocusManager.FocusedElement="{Binding ElementName=tv}"
    8. >
    9. <Window.DataContext>
    10. <my:DirectoryTree/>
    11. </Window.DataContext>


    hier wird ein statisches Feld im Viewmodel addressiert (wpf-treeview-tut):

    XML-Quellcode

    1. <Window x:Class="MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. Title="MainWindow" Height="350" Width="525"
    5. xmlns:my="clr-namespace:WpfFilesystemTreeView"
    6. DataContext="{x:Static my:MainModel.Root}">


    Auf jeden Fall: Wenn dem Window kein Datacontext gesetzt ist, gibts auch kein Binding-Picking, und du suchst dich dumm und dämlich nach Binding-Mismatches.