MVVM OpenSource Communityprojekt - HomeStorage

  • WPF

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

    Danke für die netten Worte.
    JA, langsam wirds eh wieder, nur die harten kommen durch. :)

    Spätestens ende nächster Woche geht es hier wieder weiter.
    Sind im übrigen in der zwischenzeit Fragen aufgetaucht welche wir vieleicht noch durchgehen sollten??

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

    ThuCommix schrieb:

    Somit bleiben alle Ef Verweise im DAL Layer und falls du das Framework ändern willst musst du nur die Extension und die Repository Klasse anpassen. Durch das exposen das IQueryable im Repository hast du alle Möglichkeiten

    Soweit habe ich mir das jetzt angesehen und auch verstanden. Ist ja nicht um so viel anders als meine Basisklasse. Die QueryableExtensions sind cool, nur....

    Oft kommt es vor das man kompliziertere Abfragen hat wo man mehrere Includes und Joinings benötigt. Diese kann man nun nicht "einpflanzen" ohne einen Verweis auf EF zu benötigen oder habe ich was übersehen?
    Ich weis, das kommt nicht so oft vor, aber dennoch oft genug. Aber ich gebe dir recht, man benötigt dann nicht zwingend für JEDE Entität eine Klasse was schon mal super ist, hätte ich eigendlich auch dran denken können :/ .

    Aber wenn ich nichts übersehen habe muss ich ja nur bei meiner Basisklasse das MustInherit entfernen und habe eigendlich dann beides. 8|
    Zumindest wenn ich nix übersehen habe. ?(

    Danke für deine Unterstützung
    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Habe mal wieder Commited.

    Die Repository Klasse wurde nach den erkenntnissen von @ThuCommix abgeändert und ist nur auch direkt instanzierbar.
    RelayCommand und ViewModelBase Klasse hinzugefügt.

    Die RelayCommand unterscheidet sich allerdings von einer RelayCommand Klasse wie man sie normalerweise in WPF Anwendungen verwendet.
    Hintergrund: Da wir hier unter .Net Standard Arbeiten gibt es keinen CommandManager wie im .Net Framework wodurch uns ein paar Funktionalitäten fehlen.
    Eine davon ist das man hier nicht automatisch diese "Magie" hat das ein Button automatisch Anhand von CanExecute des Commands Enabled True/False ist.
    Deshalb wird eine Eigenschaft im RelayCommand (IsEnabled) gesetzt welche dies erledigt. Allerdings muss diese im ViewModel auch gesetzt werden. Dazu kommen wir aber dann nochmal wenn wir das erste ViewModel erstellen.

    Fragen bis hier her? Einwende bis hier her?

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

    Da es keine Fragen und/oder einwende gibt mach ich mal weiter.

    Wir werden die ersten ViewModels und natürlich die ersten Views dazu machen.
    Wir fangen an mit einem "HauptView", also unserem Hauptfenster.

    @MichaHo hast du schon vorstellungen wie das Hauptfenster aussehen soll und welche Funktionalitäten es haben soll?
    Soll oben ein Klassisches Menü sein oder willst du eher ein seitliches Menü haben?
    Soll der Haupt-Arbeitsbereich unterteilt sein?

    Ich mache das meist so das ich ganz klassisch eine Skizze anfertige wie mein View aussehen soll. Anhand der Skizze fertige ich ein ViewModel. Wir wissen ja, ein ViewModel bildet im Grunde das View ab.
    Hier bin ich auf eure Vorschläge gespannt, immerhin soll es ja ein Communityprojekt sein. Bringt eure Ideen ein und macht euch gedanken darüber wie die App aussehen soll. (erstmal nur im Groben) und was alles im ersten Hauptview zu sehen sein soll.

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

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

    Nofear23m schrieb:

    Hintergrund: Da wir hier unter .Net Standard Arbeiten...
    [...]
    Fragen bis hier her? Einwende bis hier her?

    Neulich habich auf Arbeit irrtümlich unserer WinForms-Anwendung ein .Net-Standard-Projekt zugefügt - das Ergebnis war erhebliche Desorientierung meinerseits, weil alles mögliche auf einmal anners zu ticken schien.
    Kannst du vlt. kurz was zu .Net-Standard sagen, wasses damit auf sich hat?
    Ist das ein nues, parallelles Framework zum Framework wie ich es kenne?
    War und ist mir völlig neu.

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

    Hallo @ErfinderDesRades

    Der größte unterschied ist wohl das man .Net Standard für viel mehr "Bereiche" verwenden kann. Also auch z.b. von UWP Apps oder Xamarin Applikationen aus referenzieren kann.
    Es erklärt sich das hierdurch .Net Standard nicht den selben funktionsumfang hat da es ja z.b. eben unter Xamarin funzt und somit auf Android oder IOS lauffähig ist.

    Hier würde ich sagen ist es sehr gut erklärt.
    PS: Bitte nicht mit .Net Core verwechseln.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    .NET Standard ist der Vertrag den alle Framework Runtimes (Desktop CLR, Mono, Core) erfüllen müssen. Du kannst es dir im Grunde genommen wie ein Interface vorstellen.

    docs.microsoft.com/de-de/dotnet/standard/net-standard

    In der ersten Tabelle kannst du sehen welche Version der jeweiligen Runtime welche .NET Standard Version implementiert.
    Wie schon gesagt, ist es eine Art Interface, um für mehrere Plattformen (Xamarin, .NET-Framework, .NET Core, ...) gleichzeitig Bibliotheken zur Verfügung stellen zu können. .NET Standard liefert Dir lokal im SDK in einer netstandardxx.dll alles an Klassen samt Implementierungen mit, um vorläufig entwickeln zu können. Später dann wenn Du das Projekt kompilierst, kriegst Du eine netstandardxx.dll im bin-Ordner mit. Allerdings enthält diese dann keine Implementierungen oder sonstiges, sondern nur Attribute für Type Forwarding. Somit wird dem referenzierenden Projekt dann mitgeteilt, welche Klassen die .NET-Standard-DLL haben will und dann werden die konkreten aus dem Projekt verwendet (z. B. dann die aus der mscorlib). .NET Standard ist also keine unabhängige Implementierung von allem, sondern eben nur eine Schnittstelle. Die Implementierung kommt dann aus den jeweiligen Plattformen hinter den Projekten, die die Library referenzieren. Das ist extrem nützliches Zeug, was Microsoft da entwickelt und ich bin mir relativ sicher, dass da zukünftig noch so viel kommen wird, dass auch alles mal komplett Cross Platform wird. Dann kann man u. U. drauf verzichten, sich zwangsweise mit dem ganzen Webgebastel für Desktop-Anwendungen auseinanderzusetzen (Electron und co.), was ich persönlich echt total ätzend finde. Verstehe bis heute nicht wie z. B. Microsoft VS Code so umsetzen konnte, ohne vom Stuhl zu kippen, aber jedem das Seine. :D
    Wie auch immer. Mit .NET Core 3.0 usw. kommt dann auch ziemlich nützliches Zeugs dazu (zwar noch keine Plattformunabhängigkeit für WPF, aber trotzdem nützliche Features auch dafür ^^). Und .NET Standard usw. wird ja ständig erweitert, auch wenn es schon recht viel gibt. Das Einzige, was mir da mal gefehlt hat, war 'ne Möglichkeit, Bilder umzusetzen, da ich meine ViewModel-Projekte auch auf .NET-Standard umgestellt habe. Vielleicht weiß da ja jemand Rat. ;) Geht auf jeden Fall alles in Richtung Zukunft. Daher rate ich auch jedem extrem, sich damit auseinanderzusetzen, @ErfinderDesRades.

    Grüße
    #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 :!:
    Danke @ThuCommix und @Trade für die detailierte Erläuterung. :thumbsup:
    Super erklärt, ich finde auch das man versuchen sollte seine ViewModel-Klassen in .Net Standard zu schreiben da man später vielleicht wirklich einen nutzen daraus ziehen könnte.
    In diesem Projekt ist es auch das erst mal das ich das versuche, mal sehen auf welche Hindernisse ich stoßen werde.

    Das erste war jetzt mal die RelayCommand Klasse. Ich hoffe das ich hier die richtige Lösung gefunden habe, hast du da schon erfahrungen @Trade?

    Mal sehen was noch kommt. Ich habe zwar schon einige Klassen immer wieder mal in .NetStandard Bibliotheken gehabt aber ein ViewModel noch nicht.

    Danke nochmals an euch zwei für die gute Beschreibung.
    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Also Command-Klassen umsetzen ging bei mir mit .NET Standard 2.0 ohne Probleme. Brauchst ja an sich nur das ICommand-Interface.

    Grüße
    #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 :!:
    Ja, aber da kein CommandManager vorhanden ist klappt die "Magie" das ein Button über das CanExecute automatisch Enabled oder Desabled wird nicht.

    So sieht normal meine RelayCommand Klasse aus. Das klappt aber unter .Net Standard nicht.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class RelayCommand : Implements ICommand
    2. #Region "Fields"
    3. ReadOnly _execute As Action(Of Object)
    4. ReadOnly _canExecute As Predicate(Of Object)
    5. #End Region
    6. #Region " Constructors"
    7. ''' <summary>
    8. ''' Erstellt einen neuen Command welcher NUR Executed werden kann.
    9. ''' </summary>
    10. ''' <param name="execute">The execution logic.</param>
    11. ''' <remarks></remarks>
    12. Public Sub New(execute As Action(Of Object))
    13. Me.New(execute, Nothing)
    14. End Sub
    15. ''' <summary>
    16. ''' Erstellt einen neuen Command welcher sowohl die Execute als auch die CanExecute Logik beinhaltet.
    17. ''' </summary>
    18. ''' <param name="execute">Die Logik für Execute.</param>
    19. ''' <param name="canExecute">Die Logik für CanExecute.</param>
    20. ''' <remarks></remarks>
    21. Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
    22. If execute Is Nothing Then
    23. Throw New ArgumentNullException("execute")
    24. End If
    25. _execute = execute
    26. _canExecute = canExecute
    27. End Sub
    28. #End Region
    29. #Region " ICommand Members "
    30. ''' <summary>
    31. ''' Setzt die CanExecute-Methode des ICommand-Interfaces auf True oder False
    32. ''' </summary>
    33. ''' <param name="parameter"></param>
    34. ''' <returns>Gibt zurück ob die Aktion ausgeführt werden kann oder nicht</returns>
    35. ''' <remarks>
    36. ''' Benutzt DebuggerStepThrough from System.Diagnostics
    37. ''' Der Debugger überspringt diese Prozedur also, es sei den es wird explizit ein Haltepunkt gesetzt.
    38. ''' </remarks>
    39. <DebuggerStepThrough>
    40. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    41. Return _canExecute Is Nothing OrElse _canExecute(parameter)
    42. End Function
    43. ''' <summary>
    44. ''' Event welches geworfen wird wenn die Propertie CanExecuteChanged sich ändert.
    45. ''' </summary>
    46. ''' <remarks></remarks>
    47. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    48. AddHandler(value As EventHandler)
    49. If _canExecute IsNot Nothing Then
    50. AddHandler CommandManager.RequerySuggested, value
    51. End If
    52. End AddHandler
    53. RemoveHandler(value As EventHandler)
    54. If _canExecute IsNot Nothing Then
    55. RemoveHandler CommandManager.RequerySuggested, value
    56. End If
    57. End RemoveHandler
    58. RaiseEvent(sender As Object, e As EventArgs)
    59. End RaiseEvent
    60. End Event
    61. ''' <summary>
    62. ''' Führt die Prozedur Execute des ICommand.Execute aus
    63. ''' </summary>
    64. ''' <param name="parameter"></param>
    65. ''' <remarks></remarks>
    66. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    67. _execute(parameter)
    68. End Sub
    69. #End Region
    70. End Class




    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
    Stimmt, an den habe ich nicht gedacht, weil ich ihn nicht benutze (hatte da mal Probleme mit feuern von Events und so). Dann weiß ich leider auch nicht weiter. :(

    Grüße
    #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 :!:
    Achso OK. Danke trotzdem.

    Na ich habs jetzt in dem Projekt so gelöst das ich in der RelayCommand Klasse ein Property IsEnabled habe ich dieses kann ich vom ViewModel aus setzen. So funzt es.

    Probleme? Also unter .Net Framework klappt die Klasse von eben wunderbar und alles klappt automatisch. Echt super eigentlich.
    Naja, wer weis, vielleicht findet der CommandManager noch den Weg ins .Net Standard. Wäre schön.

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

    Nofear23m schrieb:

    Probleme? Also unter .Net Framework klappt die Klasse von eben wunderbar und alles klappt automatisch.
    Kannst glaub n Request dafür auf GitHub erstellen.

    Grüße
    #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 :!:
    @Nofear23m

    Hey,
    schau dir mal die Implementierung des DelegateCommands im Prism Projekt an.
    Dieser bietet einen mmn schönen Trade-Off zwischen dem gewünschten CommandManager und deiner aktuellen IsEnabled Version.

    Allgemein kannst du mit dieser Implementierung die CanExecuteChanged-Events auf folgende Art vom VM auslösen:

    C#-Quellcode

    1. class MyViewModel : INotifyPropertyChanged {
    2. // ..
    3. // Some properties which call PropertyChanged.
    4. // ..
    5. public ICommand MyCommand { get; } = new DelegateCommand(Execute, CanExecute)
    6. .ObservesProperty(() => MyPropertyA)
    7. .ObservesProperty(() => MyPropertyB);
    8. }


    Jedesmal, wenn sich eine der beiden Eigenschaften ändert, wird automatisch CanExecute neu ausgewertet.
    Es gibt sicherlich einige Spezialfälle, in denen diese Art von Command nicht anwendbar ist (hierfür gibt es dann aber auch noch ein RaiseCanExecuteChanged event), aber in den meisten Fällen müssen Commands ja wirklich nur auf INotifyPropertyChanged hören.

    Die Dokumentation (speziell der ObservesProperty) Header zeigt detailierter, wie man ihn benutzen kann.
    Hallo @shad

    Danke für den Beitrag, habe ich mir angesehen. Auch nicht schlecht gemacht, ich stoße die Auswertung eben im Setter des Propertys an. Aber das ist auch nicht schlecht.
    Werde ich vielleicht in meine Klasse mit einbauen.

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

    Habe ich nun eingebaut. Klappt auch gut. Finde ich sehr gut gelöst. Danke für den Hinweis.
    Finde ich toll so ein Communityprojekt, so kommt man immer wieder auf was neues/besseres.

    So sieht nun die RelayCommand-Klasse aus wenn ich sie richtig übersetzt habe...

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Linq.Expressions
    2. Imports System.Threading
    3. Imports System.Windows.Input
    4. Public Class RelayCommand : Implements ICommand
    5. #Region "Fields"
    6. Private _execute As Action(Of Object)
    7. Private _canExecute As Func(Of Boolean)
    8. Private _observedPropertiesExpressions As HashSet(Of String) = New HashSet(Of String)
    9. Private ReadOnly _synchronizationContext As SynchronizationContext
    10. #End Region
    11. #Region "Constructor"
    12. Public Sub New(execute As Action(Of Object))
    13. Me.New(execute, Function() True)
    14. End Sub
    15. ''' <summary>
    16. ''' Erstellt einen neuen Command welcher sowohl die Execute als auch die CanExecute Logik beinhaltet.
    17. ''' </summary>
    18. ''' <param name="execute">Die Logik für Execute.</param>
    19. ''' <param name="canExecute">Die Logik für CanExecute.</param>
    20. ''' <remarks></remarks>
    21. Public Sub New(execute As Action(Of Object), canExecute As Func(Of Boolean))
    22. If execute Is Nothing Then
    23. Throw New ArgumentNullException("execute")
    24. End If
    25. _synchronizationContext = SynchronizationContext.Current
    26. _execute = execute
    27. _canExecute = canExecute
    28. End Sub
    29. #End Region
    30. Protected Overridable Sub OnCanExecuteChanged()
    31. If ((Not (_synchronizationContext) Is Nothing) _
    32. AndAlso (_synchronizationContext IsNot SynchronizationContext.Current)) Then
    33. _synchronizationContext.Post(Sub() RaiseEvent CanExecuteChanged(Me, EventArgs.Empty), Nothing)
    34. Else
    35. RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    36. End If
    37. End Sub
    38. ''' <summary>
    39. ''' Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    40. ''' </summary>
    41. ''' <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
    42. ''' <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
    43. Protected Sub ObservesPropertyInternal(Of T)(ByVal propertyExpression As Expression(Of Func(Of T)))
    44. If _observedPropertiesExpressions.Contains(propertyExpression.ToString) Then
    45. Throw New ArgumentException("{propertyExpression.ToString()} is already being observed.", NameOf(propertyExpression))
    46. Else
    47. _observedPropertiesExpressions.Add(propertyExpression.ToString)
    48. PropertyObserver.Observes(propertyExpression, Sub() RaiseCanExecuteChanged())
    49. End If
    50. End Sub
    51. ''' <summary>
    52. ''' Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    53. ''' </summary>
    54. ''' <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
    55. ''' <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
    56. ''' <returns>The current instance of DelegateCommand</returns>
    57. Public Function ObservesProperty(Of T)(ByVal propertyExpression As Expression(Of Func(Of T))) As RelayCommand
    58. ObservesPropertyInternal(propertyExpression)
    59. Return Me
    60. End Function
    61. ''' <summary>
    62. ''' Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    63. ''' </summary>
    64. ''' <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
    65. ''' <returns>The current instance of DelegateCommand</returns>
    66. Public Function ObservesCanExecute(ByVal canExecuteExpression As Expression(Of Func(Of Boolean))) As RelayCommand
    67. _canExecute = canExecuteExpression.Compile
    68. ObservesPropertyInternal(canExecuteExpression)
    69. Return Me
    70. End Function
    71. #Region "ICommand Members"
    72. <DebuggerStepThrough>
    73. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    74. Return _canExecute()
    75. End Function
    76. Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    77. Public Sub RaiseCanExecuteChanged()
    78. RaiseEvent CanExecuteChanged(Me, New EventArgs())
    79. End Sub
    80. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    81. _execute(parameter)
    82. End Sub
    83. #End Region
    84. End Class


    Dazu dann PropertyObserver und PropertyObserverNode...

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Linq.Expressions
    3. Friend Class PropertyObserver
    4. Private ReadOnly _action As Action
    5. Private Sub New(ByVal propertyExpression As Expression, ByVal action As Action)
    6. MyBase.New
    7. _action = action
    8. SubscribeListeners(propertyExpression)
    9. End Sub
    10. Private Sub SubscribeListeners(ByVal propertyExpression As Expression)
    11. Dim propNameStack = New Stack(Of String)
    12. While TryCast(propertyExpression, MemberExpression) IsNot Nothing
    13. propNameStack.Push(DirectCast(propertyExpression, MemberExpression).Member.Name) ' Records the name of each property.
    14. propertyExpression = DirectCast(propertyExpression, MemberExpression).Expression
    15. End While
    16. If Not (TypeOf propertyExpression Is ConstantExpression) Then
    17. Throw New NotSupportedException("Operation not supported for the given expression type. " + "Only MemberExpression and ConstantExpression are currently supported.")
    18. End If
    19. Dim propObserverNodeRoot = New PropertyObserverNode(CType(propNameStack.Pop(), String), _action)
    20. Dim previousNode As PropertyObserverNode = CType(propObserverNodeRoot, PropertyObserverNode)
    21. For Each propName As String In propNameStack
    22. Dim currentNode As PropertyObserverNode = New PropertyObserverNode(propName, _action)
    23. previousNode.Next = currentNode
    24. previousNode = currentNode
    25. Next
    26. Dim propOwnerObject As Object = DirectCast(propertyExpression, ConstantExpression).Value
    27. If Not (TypeOf propOwnerObject Is INotifyPropertyChanged) Then
    28. Throw New InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " +
    29. $"owns '{propObserverNodeRoot.PropertyName}' property, but the object does not implements INotifyPropertyChanged.")
    30. End If
    31. propObserverNodeRoot.SubscribeListenerFor(DirectCast(propOwnerObject, INotifyPropertyChanged))
    32. End Sub
    33. ''' <summary>
    34. ''' Observes a property that implements INotifyPropertyChanged, and automatically calls a custom action on
    35. ''' property changed notifications. The given expression must be in this form: "() => Prop.NestedProp.PropToObserve".
    36. ''' </summary>
    37. ''' <param name="propertyExpression">Expression representing property to be observed. Ex.: "() => Prop.NestedProp.PropToObserve".</param>
    38. ''' <param name="action">Action to be invoked when PropertyChanged event occours.</param>
    39. Friend Shared Function Observes(Of T)(ByVal propertyExpression As Expression(Of Func(Of T)), ByVal action As Action) As PropertyObserver
    40. Return New PropertyObserver(propertyExpression.Body, action)
    41. End Function
    42. End Class


    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Reflection
    3. ''' <summary>
    4. ''' Represents each node of nested properties expression and takes care of
    5. ''' subscribing/unsubscribing INotifyPropertyChanged.PropertyChanged listeners on it.
    6. ''' </summary>
    7. Class PropertyObserverNode
    8. Private ReadOnly _action As Action
    9. Private _inpcObject As INotifyPropertyChanged
    10. Public ReadOnly Property PropertyName As String
    11. Public Property [Next] As PropertyObserverNode
    12. Public Sub New(ByVal propertyName As String, ByVal action As Action)
    13. MyBase.New
    14. Me.PropertyName = propertyName
    15. _action = New Action(Sub() DoAction(action))
    16. End Sub
    17. Private Sub DoAction(action As Action)
    18. action?.Invoke()
    19. If ([Next] Is Nothing) Then
    20. Return
    21. End If
    22. [Next].UnsubscribeListener()
    23. GenerateNextNode()
    24. End Sub
    25. Public Sub SubscribeListenerFor(ByVal inpcObject As INotifyPropertyChanged)
    26. _inpcObject = inpcObject
    27. AddHandler _inpcObject.PropertyChanged, AddressOf OnPropertyChanged
    28. If (Not ([Next]) Is Nothing) Then
    29. GenerateNextNode()
    30. End If
    31. End Sub
    32. Private Sub GenerateNextNode()
    33. Dim propertyInfo = _inpcObject.GetType.GetRuntimeProperty(PropertyName) ' TODO: To cache, if the step consume significant performance. Note: The type of _inpcObject may become its base type or derived type.
    34. Dim nextProperty = propertyInfo.GetValue(_inpcObject)
    35. If (nextProperty Is Nothing) Then
    36. Return
    37. End If
    38. If Not (TypeOf nextProperty Is INotifyPropertyChanged) Then
    39. Throw New InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " +
    40. $"owns '{[Next].PropertyName}' property, but the object does not implements INotifyPropertyChanged.")
    41. End If
    42. [Next].SubscribeListenerFor(DirectCast(nextProperty, INotifyPropertyChanged))
    43. End Sub
    44. Private Sub UnsubscribeListener()
    45. If (Not (_inpcObject) Is Nothing) Then
    46. AddHandler _inpcObject.PropertyChanged, AddressOf OnPropertyChanged
    47. End If
    48. [Next]?.UnsubscribeListener()
    49. End Sub
    50. Private Sub OnPropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs)
    51. ' Invoke action when e.PropertyName == null in order to satisfy:
    52. ' - DelegateCommandFixture.GenericDelegateCommandObservingPropertyShouldRaiseOnEmptyPropertyName
    53. ' - DelegateCommandFixture.NonGenericDelegateCommandObservingPropertyShouldRaiseOnEmptyPropertyName
    54. If ((e?.PropertyName = PropertyName) _
    55. OrElse (e?.PropertyName Is Nothing)) Then
    56. _action?.Invoke
    57. End If
    58. End Sub
    59. End Class



    Anwendung dann hier in einem Beispiel:

    VB.NET-Quellcode

    1. Public Class TestVm
    2. Inherits ViewModelBase
    3. Private _test As Boolean
    4. Public Property Test() As Boolean
    5. Get
    6. Return _test
    7. End Get
    8. Set(ByVal value As Boolean)
    9. _test = value
    10. RaisePropertyChanged()
    11. End Set
    12. End Property
    13. Private _test1 As Boolean
    14. Public Property Test1() As Boolean
    15. Get
    16. Return _test1
    17. End Get
    18. Set(ByVal value As Boolean)
    19. _test1 = value
    20. RaisePropertyChanged()
    21. End Set
    22. End Property
    23. Private _testCommand As RelayCommand
    24. Public Property TestCommand() As RelayCommand
    25. Get
    26. If _testCommand Is Nothing Then _
    27. _testCommand = New RelayCommand(AddressOf TestCommand_Execute,
    28. AddressOf TestCommand_CanExecute).ObservesProperty(Function() Test).ObservesProperty(Function() Test1)
    29. Return _testCommand
    30. End Get
    31. Set(ByVal value As RelayCommand)
    32. _testCommand = value
    33. RaisePropertyChanged()
    34. End Set
    35. End Property
    36. Private Function TestCommand_CanExecute() As Boolean
    37. Return Test AndAlso Test1
    38. End Function
    39. Private Sub TestCommand_Execute(obj As Object)
    40. End Sub
    41. End Class



    Gibt es vieleicht noch verbesserungsvorschläge??

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

    ErfinderDesRades schrieb:

    man könnte ObservesProperty auch als ParamArray designen

    Werde ich machen, gute Idee. Danke

    @MichaHo super. Dann können wir ja schon mal mit dem ersten ViewModel beginnen. Uns fehlt zwar noch ein wenig an der Intrastruktur aber das machen wir später.

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