Command's in WPF

  • WPF

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

    Command's in WPF

    Hallo liebes Forum,

    ich bin nun schon eine Weile mit der WPF zugange und langsam, wo man das Bindingkonzept verstanden hat, beginnt es echt Spaß zu machen.
    Nun wollte ich ein wenig weiter gehen und mich mit dem Thema Command's beschäftigen.

    Doch hier habe ich einige Verständnisprobleme. Allerdings habe ich gehört, dass das Thema Commands im Tutorial von Nofear23m noch behandelt wird. Deswegen werde ich bis dorthin warten.
    Mir wurde allerdings auch gesagt, dass die RelayCommand Klasse (siehe Projekte von Nofear23M) die eigentlichen Commands "überflüssig macht" und vieles vereinfacht.

    Eventuell könnte mir einer (wahrscheinlich Sascha:)) kurz (am besten an einem Beispiel, dann lernt man wie ich finde am meisten) erklären, wie man diese Hilfsklasse korrekt anwendet.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor

    Commands zusammengefasst in RelayCommand

    Hi.

    RelayCommands ist eine Implementierung der Commands in einer Klasse zur Wiederverwendung.

    Es gibt zudem auch andere Bezeichnungen der Klasse (DelegateCommand) , aber es trifft es ein wenig auf den Nagel mit "Relais-Kommando".

    Zum Verständnis:
    Die Commands sind nicht überflüssig, sondern in der "RelayCommand" Klasse niedergeschrieben, und zur Verwendung als Eigenschaften in den jeweiligen ViewModels "abgelegt".

    Eine einfache Erklärung ist folgendes:
    Das Interface ICommand ist was WPF ermöglicht (in XAML) die "Command"-Eigenschaft in irgendeiner Klasse als Funktionsaufruf anzugeben.

    Analogie zu Windowsforms/WPF-Codebehind:

    Die UI-Events wie Klicken, Scrollen etc. sind Im Codebehind, und mit ICommand (respektive die Commands) können sie in irgendeine Klasse "verlagert" werden.
    Sehr nützlich wenn MVVM zum einsatz kommt.

    Und warum sowas?

    Es gibt Programme, die mit tausenden von Menüeinträgen den Benutzer erschlagen, und alle sind mit einer Ereignislogik versehen.
    Wer nun in WPF die ICommand/Command einpflegt, muss einen imensen Aufwand an wiederholenden Quelltext abschnitten Tippen.
    Hier können die Lesbarkeit und die Fehleranzahl die Programmierarbeit erschweren.
    Mit dem RelayCommand wird das vereinfacht, und als Bonus, in Eigenschaften der ViewModel-Klasse (natürliches Vorkommen der Relay-Commands) Implementiert.

    VB.NET-Quellcode

    1. Public Class RelayCommand
    2. Implements ICommand


    Das ICommand hat eine Subroutine "Execute"

    "Definiert die Methode, die aufgerufen wird, wenn der Befehl aufgerufen wird."

    Zudem hat es einen Event "CanExecuteChanged"

    "Tritt ein, wenn Änderungen auftreten, die sich auf die Ausführung des Befehls auswirken."

    Und zu guter letzt eine Funktion "CanExecute"

    "Vom Befehl verwendete Daten. Wenn der Befehl keine Datenübergabe erfordert, kann das Objekt auf null festgelegt werden."


    In einem Beispiel eines Buttons.

    VB.NET-Quellcode

    1. Private _webSeiteAufrufen As ICommand
    2. Public ReadOnly Property WebSeiteAufrufenCommand() As ICommand
    3. Get
    4. If _webSeiteAufrufen Is Nothing Then
    5. _webSeiteAufrufen = New RelayCommand(Sub() WebseiteAufrufen(), Function() True)
    6. End If
    7. Return _webSeiteAufrufen
    8. End Get
    9. End Property
    10. Private Sub WebseiteAufrufen()
    11. 'Mach was...
    12. Process.Start("https://www.vb-paradise.de/index.php")
    13. End Sub



    Im XAML des Buttons in der Eigenschaft Command steht dann:

    VB.NET-Quellcode

    1. <Button
    2. Command="{Binding WebSeiteAufrufenCommand}"
    3. Content="VB-Paradise Webseite"
    4. ToolTip="Drücke hier, um die Webseite im standard Browser zu besuchen." />


    Ein wenig aufdröseln...

    Mit der Eigenschaft "WebSeiteAufrufenCommand" ist die XAML-ViewModel Verkabelung definiert, und kann verwendet werden.

    VB.NET-Quellcode

    1. _webSeiteAufrufen = New RelayCommand(Sub() WebseiteAufrufen(), Function() True)


    Das hier ist dann die Verwendung des RelayCommands.

    Eine Subroutine namens "WebseiteAufrufen" wird dem RelayCommand bekanntgegeben.
    Mit dem zweiten Parameter wird die Funktion einfach auf True festgelegt, um den Button zu Aktivieren (Enable).

    Wann immer der Button geklickt wird, macht das RelayCommand seine Zauberwirkung und prüft ob gedrückt werden darf und führt es dann aus.
    Hier in diesem Fall prüft er nicht sondern ist auf True() festgelegt, und somit ist der Button "klickbar".
    Die Subroutine "WebseiteAufrufen" wird ausgeführt und Viola sind wir hier im Forum... ;)


    "RelayCommand(Sub() WebseiteAufrufen(), Function() True)"

    Im ersten Parameter kann sogar eine asynchrone Methode angegeben werden.
    Im Zweiten Parameter wird eine Funktion des Typs Boolean definiert.
    Der zweite Parameter wird zur bestimmung definiert, ob ein Command (z.B. Button) ausgeführt werden kann.

    Klassisches Beispiel: Kein Text in einem Textfeld, der Button bleibt aus, und wenn mehr als 3 Buchstaben dann Button einschalten.

    Als Konvention hat sich das Muster des anhängens von dem Wort "Command" am Ende der Eigenschaft eingespielt.

    "DuKochsterstmalKaffeCommand" -> DuKochsterstmalKaffe & Command :)

    Somit kann dann mithilfe von Snippets (Codeausschnitten) die Programmierarbeit erleichtert werden.

    Hier ein meine Implementierung eines Snippets für VB.

    XML-Quellcode

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    3. <CodeSnippet Format="1.0.0">
    4. <Header>
    5. <Title>Eigenschaft vom Typ ICommand die ein RelayCommand mit Subroutine zurück gibt.</Title>
    6. <Author>Jaroslav Mitrović</Author>
    7. <Description>Erstellt eine Eigenschaft vom Typ ICommand, für die Verwendung als Kommando für eine View zum ViewModel.
    8. Anmerkung: Es ist als Fire-and-Forget zu verstehen.
    9. Eine Subroutine wird erstellt und im RelayCommand aufgerufen. Die CanExecute-Funktion ist mit True vordefiniert.
    10. Bitte ändern sie den Rückgabewert mit Ihrer Validierung.</Description>
    11. <Shortcut>relsub</Shortcut>
    12. </Header>
    13. <Snippet>
    14. <Declarations>
    15. <Literal>
    16. <ID>Eigenschaftsname</ID>
    17. <Type>String</Type>
    18. <ToolTip>Ersetzen Sie hier den Eigenschaftsnamen.</ToolTip>
    19. <Default>Name</Default>
    20. </Literal>
    21. </Declarations>
    22. <Code Language="VB" Kind="method decl">
    23. <![CDATA[
    24. Private _$Eigenschaftsname$ As ICommand
    25. Public ReadOnly Property $Eigenschaftsname$Command() As ICommand
    26. Get
    27. If _$Eigenschaftsname$ Is Nothing Then
    28. _$Eigenschaftsname$ = New RelayCommand(Sub() $Eigenschaftsname$, Function()
    29. 'Validierung für CanExecute
    30. Return True
    31. End Function)
    32. End If
    33. Return _$Eigenschaftsname$
    34. End Get
    35. End Property
    36. Private Sub $Eigenschaftsname$()
    37. $end$
    38. End Sub]]></Code>
    39. </Snippet>
    40. </CodeSnippet>
    41. </CodeSnippets>



    Hier die RealyCommand-Datei die ich nutze.

    VB.NET-Quellcode

    1. Public Class RelayCommand
    2. Implements ICommand
    3. Private ReadOnly _execute As Action
    4. Private ReadOnly _canExecute As Func(Of Boolean)
    5. 'Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    6. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    7. AddHandler(value As EventHandler)
    8. If _canExecute IsNot Nothing Then
    9. AddHandler CommandManager.RequerySuggested, value
    10. End If
    11. End AddHandler
    12. RemoveHandler(value As EventHandler)
    13. If _canExecute IsNot Nothing Then
    14. RemoveHandler CommandManager.RequerySuggested, value
    15. End If
    16. End RemoveHandler
    17. RaiseEvent(sender As Object, e As EventArgs)
    18. 'This is the RaiseEvent block
    19. CommandManager.InvalidateRequerySuggested()
    20. End RaiseEvent
    21. End Event
    22. 'Public Sub RaiseCanExecuteChanged()
    23. ' RaiseEvent CanExecuteChanged(Me, New EventArgs())
    24. 'End Sub
    25. Public Sub New(execute As Action)
    26. Me.New(execute, Nothing)
    27. End Sub
    28. Public Sub New(execute As Action, canExecute As Func(Of Boolean))
    29. If execute Is Nothing Then
    30. Throw New ArgumentNullException(NameOf(execute))
    31. End If
    32. Me._execute = execute
    33. Me._canExecute = canExecute
    34. End Sub
    35. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    36. If _canExecute Is Nothing Then
    37. Return True
    38. Else
    39. Return _canExecute()
    40. End If
    41. End Function
    42. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    43. _execute()
    44. End Sub
    45. Public Sub OnCanExecuteChanged()
    46. RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    47. End Sub
    48. End Class
    49. Public Class RelayCommand(Of T)
    50. Implements ICommand
    51. Private ReadOnly _execute As Action(Of T)
    52. Private ReadOnly _canExecute As Func(Of T, Boolean)
    53. 'Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    54. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    55. AddHandler(value As EventHandler)
    56. If _canExecute IsNot Nothing Then
    57. AddHandler CommandManager.RequerySuggested, value
    58. End If
    59. End AddHandler
    60. RemoveHandler(value As EventHandler)
    61. If _canExecute IsNot Nothing Then
    62. RemoveHandler CommandManager.RequerySuggested, value
    63. End If
    64. End RemoveHandler
    65. RaiseEvent(sender As Object, e As EventArgs)
    66. 'This is the RaiseEvent block
    67. CommandManager.InvalidateRequerySuggested()
    68. End RaiseEvent
    69. End Event
    70. 'Public Sub RaiseCanExecuteChanged()
    71. ' RaiseEvent CanExecuteChanged(Me, New EventArgs())
    72. 'End Sub
    73. Public Sub New(execute As Action(Of T))
    74. Me.New(execute, Nothing)
    75. End Sub
    76. Public Sub New(execute As Action(Of T), canExecute As Func(Of T, Boolean))
    77. If execute Is Nothing Then
    78. Throw New ArgumentNullException(NameOf(execute))
    79. End If
    80. Me._execute = execute
    81. Me._canExecute = canExecute
    82. End Sub
    83. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    84. If _canExecute Is Nothing Then
    85. Return True
    86. Else
    87. Return _canExecute(CType(parameter, T))
    88. End If
    89. End Function
    90. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    91. _execute(CType(parameter, T))
    92. End Sub
    93. Public Sub OnCanExecuteChanged()
    94. RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    95. End Sub
    96. End Class



    Diese liegt in einem Ordner namens "Helpers", oder "ViewModels", wie es mir gerade zusagt...

    Stolpersteine die ich immer hatte bis ein wenig der Nebel sich gelegt hatte, um RelayCommands zu verstehen.

    Um nicht für jeden ICommand jeweils CanExectue und CanExecuteChanged auszuformulieren, hilft es dieses "auszulagern" in die Klasse RelayCommand.

    Dann werden Lambda-Ausdrücke verwendet, um den Schreibaufwand zu reduzieren.
    Dieser Aufwand ist aber immernoch zu viel, und mit hilfe von Snippets reduzieren Programmierer diesen abermals.

    Eine Erklärung die ich mal verwendet habe ist vereinfacht...

    Lege ein Kabel von der Oberfläche zum ViewModel, sage was es wann darf, und dann Delegiere was es zu tun hat...

    Kabel = XAML zu ViewModel mittels Eigenschaft vom Typ ICommand
    Was Wann Darf = Zweiter Parameter im RelayCommand als Function vom Typ Boolean
    Das Delegierte = Das auszuführende als Subroutine (auch Async) oder Function bzw. Action(Of Typ)


    Ich mach mal schluß hier (vorwest) , muss heute noch Feieren...

    Wenn es mehr zur verwirrung beigetragen hat, bitte ich um Entschuldigung, ansonsten viel Spaßß beim Programmieren.

    c.u. Joshi aus Hamburg
    Hallo @flori2212

    Ich versuchs mal im Schnelldurchlauf. Der Rest kommt dann eh genauer im Tutorial.

    Erstmal gibt es zwei arten von Commands. Die RoutedCommands und die RoutedUICommands. Beie implementieren aber das ICommand Interface. Auf dieses baut alles auf.

    Fangen wir mit einem einfachen Command an. Wir erstellen eine Klasse SetColorCommand. Diese implementiert das ICommand interface.
    Das Interface gibt ein Event und zwei Methode vor. Diese gilt es zu implementieren.

    VB.NET-Quellcode

    1. Public Class SetColorCommand
    2. Implements ICommand
    3. Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    4. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    5. 'Wo soll die Farbe geändert werden. Wir haben hier keinen Zugriff auf das ViewModel. Wir müssen da im Parameter was mitgeben.
    6. 'Ist ja doof. Wir haben keinen Zugriff auf irgendein Property des ViewModels. ;-(
    7. 'Geben wir mal nur ne meldung aus.
    8. MessageBox.Show("SetColorCommand - mit ICommand implementierung")
    9. End Sub
    10. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    11. Return True
    12. End Function
    13. End Class


    Wir instanziieren nun diese Command-Klasse in der CodeBehind:

    VB.NET-Quellcode

    1. Class MainWindow
    2. Public Sub New()
    3. ' Dieser Aufruf ist für den Designer erforderlich.
    4. InitializeComponent()
    5. ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
    6. SetColorCommand = New SetColorCommand()
    7. Me.DataContext = Me
    8. End Sub
    9. Private _setColorCommand As SetColorCommand
    10. Public Property SetColorCommand As SetColorCommand
    11. Get
    12. Return _setColorCommand
    13. End Get
    14. Set(ByVal value As SetColorCommand)
    15. _setColorCommand = value
    16. End Set
    17. End Property
    18. End Class



    Im View können wir nun einfach binden:

    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. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:CommandDemo"
    7. mc:Ignorable="d" x:Name="TestWindow"
    8. Title="MainWindow" Height="450" Width="800">
    9. <StackPanel>
    10. <Button Content="Test1" Command="{Binding SetColorCommand}"/>
    11. </StackPanel>
    12. </Window>


    Die Problematik eines solchen Commands habe ich im Code kommentiert. Es ist in einer eigenen Klasse. Ich müsste also einiges als Parameter mitgeben um überhaupt in Execute irgendwas machen zu können. Doof.
    Gut, versuchen wir es anders. In dem folgenden Beispiel erstelle ich ein RoutedUICommand, könnte aber genauso ein RoutedCommand sein.

    VB.NET-Quellcode

    1. Public Class MyCommands
    2. Public Shared Property SetColor As RoutedUICommand = New RoutedUICommand("SetColor", "SetColor", GetType(MyCommands))
    3. 'Weitere Commands hier rein....
    4. '....
    5. '....
    6. End Class


    Gut, in diesem Fall können wir die Klasse sogar rein im XAML verwenden und dort instanziieren:

    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. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:CommandDemo"
    7. mc:Ignorable="d" x:Name="TestWindow"
    8. Title="MainWindow" Height="450" Width="800">
    9. <Window.CommandBindings>
    10. <CommandBinding Command="local:MyCommands.SetColor" CanExecute="SetColorUi_CanExecute" Executed="SetColorUi_Execute"/>
    11. </Window.CommandBindings>
    12. <StackPanel>
    13. <Button Content="Test1" Command="{Binding SetColorCommand}"/>
    14. <Button Content="Test2" Command="local:MyCommands.SetColor"/>
    15. </StackPanel>
    16. </Window>


    Und die beiden Methode CanExecute und Execute sind in der CodeBehind. Wenn Ihr den Methodennamen in das Propertie im XAML Editor schreibt erstellt VS auftomatisch die Methode.

    VB.NET-Quellcode

    1. Private Sub SetColorUi_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
    2. e.CanExecute = True
    3. End Sub
    4. Private Sub SetColorUi_Execute(sender As Object, e As ExecutedRoutedEventArgs)
    5. MessageBox.Show("Nun die Commandausführung in der CodeBehind. Aber leider klappt das mit einem ViewModel auch nicht. Da haben wir keine CodeBehind.")
    6. End Sub



    So jetzt kann ich zwar in der CodeBehind bereits arbeiten, schon besser aber was bei MVVM? Da gibts keine CodeBehind. Ich kann also die Methoden Execute und CanExecute nicht generieren. Doof.
    Hier kommt die RelayCommand Klasse ins spiel, da ich dieser einfach ein Action bzw. ein Predicate mitgeben kann.

    RelayCommand-Klasse

    VB.NET-Quellcode

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


    Diese zu verwenden ist einfach und habe ich hier mit zwei weiteren Button implementiert:

    VB.NET-Quellcode

    1. Class MainWindow
    2. Public Sub New()
    3. ' Dieser Aufruf ist für den Designer erforderlich.
    4. InitializeComponent()
    5. ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
    6. SetColorCommand = New SetColorCommand()
    7. SetColorRelayCommand = New RelayCommand(AddressOf SetColorCommand_Execute)
    8. Me.DataContext = Me
    9. End Sub
    10. Private _setColorCommand As SetColorCommand
    11. Public Property SetColorCommand As SetColorCommand
    12. Get
    13. Return _setColorCommand
    14. End Get
    15. Set(ByVal value As SetColorCommand)
    16. _setColorCommand = value
    17. End Set
    18. End Property
    19. Private Sub SetColorUi_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
    20. e.CanExecute = True
    21. End Sub
    22. Private Sub SetColorUi_Execute(sender As Object, e As ExecutedRoutedEventArgs)
    23. MessageBox.Show("Nun die Commandausführung in der CodeBehind. Aber leider klappt das mit einem ViewModel auch nicht. Da haben wir keine CodeBehind.")
    24. End Sub
    25. Public ReadOnly Property SetColorRelayCommand As ICommand
    26. Private Sub SetColorCommand_Execute(obj As Object)
    27. MessageBox.Show($"Nun mit dem RelayCommand. Das ist nun auch MVVM Tauglich da es komplett mittels Binding geht. Parameter: {If(obj Is Nothing, "Nothing", obj.ToString)}")
    28. End Sub
    29. End Class


    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. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:CommandDemo"
    7. mc:Ignorable="d" x:Name="TestWindow"
    8. Title="MainWindow" Height="450" Width="800">
    9. <Window.CommandBindings>
    10. <CommandBinding Command="local:MyCommands.SetColor" CanExecute="SetColorUi_CanExecute" Executed="SetColorUi_Execute"/>
    11. </Window.CommandBindings>
    12. <StackPanel>
    13. <Button Content="Test1" Command="{Binding SetColorCommand}"/>
    14. <Button Content="Test2" Command="local:MyCommands.SetColor"/>
    15. <Button Content="Test mit RelayCommand" Command="{Binding SetColorRelayCommand}"/>
    16. <Button Content="Test mit RelayCommand mit Parameter" Command="{Binding SetColorRelayCommand}" CommandParameter="{Binding ElementName=TestWindow,Path=ActualWidth}"/>
    17. </StackPanel>
    18. </Window>


    Wenn noch Fragen offen sind einfach her damit. Commands sind eines der wichtigsten Dinge. Ohne Commands kannst du nie auf eine andere Klasse ausser eine CodeBehind binden. Du könntest keinerlei Aktionen ausführen.

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

    Hallo,

    erst ein mal vielen Dank an euch beide für die super ausführliche Antwort.
    Ich habe sie mir beide durchgelesen und muss sagen, jetzt ist mir einiges klarer.

    Ich habe zur Übung ein kleines Programm geschrieben (mit der RelayCommand Klasse von Joshi) und habe dort einen Button an einen Command im ViewModel gebunden.
    Das hat auch super funktioniert, allerdings ist mir bei deiner Methode nur nicht klar, wo sich jetzt die Methode "CanExecute" befindet. (Du hast irgendwie gesagt die wäre in der RelayCommand Klasse eingebunden?)
    Ich habe das Projekt gerade mal angehangen, dann könnt ihr euch es vielleicht mal anschauen, auch ob somst alles schön gemacht ist.

    Ich bin echt immer wieder überrascht, wie viele hilfsbereite Menschen es in dem Forum gibt, die sich wirklich Zeit nehmen um einem zu helfen. Nochmals vielen Dank an euch :)

    Viele Grüße
    Florian
    Dateien
    • CommandTest.zip

      (217,39 kB, 143 mal heruntergeladen, zuletzt: )
    ----

    WebApps mit C#: Blazor
    Hallo

    Bin mobil unterwegs und kanns mir nicht ansehen. Schau dir meinen letzten Code vom MainWindow an. Da siehst du wie das mit fem CanExecute geht. Obs nun die MainWindow oder ein ViewModel ist spielt keine Rolle.

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

    Hi flori2212.

    Das mit dem CanExecute ist in der RelayCommand verlegt.

    Um das aber anzustoßen, habe ich die Funktion im zweiten Parameter angegeben.

    Wie NoFear23 es erklärt, ist ein Prädikat was dem RelayCommand mitgegeben wird.

    VB.NET-Quellcode

    1. ReadOnly _canExecute As Predicate(Of Object)


    Hier wird das Prädikat, oder Satzaussage automatisch ausgewertet, und ist somit zauberhaft aus den Augen aus dem Sinn.

    VB.NET-Quellcode

    1. New RelayCommand(Sub() WebseiteAufrufen(), Function() True)


    Hier mal eine zweite Variante mit der Auswertung der CanExecute in einer ausführlichen Funktion.

    VB.NET-Quellcode

    1. New RelayCommand(Sub() WebseiteAufrufen(), Prädikatauswerter())
    2. Function Prädikatauswerter() As Boolean
    3. If Cool = True Then
    4. Return True 'Wenn ist Cool
    5. Else
    6. Return False 'Wenn garnicht Cool
    7. EndIf
    8. End Function


    Bitte das letzte an QuellCode nicht nutzen, habe das aus dem Kopf eingetippt...

    Aber das Prinzip ist dieses:

    RelayCommand(DasAuszuführende, DaszuPrüfende)

    Wenn ein Button immer "an" sein Soll, meist der New Knopf, dann halt "RelayCommand(Sub Machjetzt(), Function() True)"

    Oder wenn in dem ViewModel (Meinethalben auch CodeBehind) andere Eigenschaftswerte herangezogen werden, dann am besten wie Oben beim "Prädikatauswerter" Beispiel umsetzen.

    VB.NET-Quellcode

    1. Public ReadOnly Property AbbrechenCommand() As ICommand
    2. Get
    3. If _abbrechen Is Nothing Then
    4. _abbrechen = New RelayCommand(Sub() Abbrechen(), Function()
    5. 'Validierung für CanExecute
    6. Return True
    7. End Function)
    8. End If
    9. Return _abbrechen
    10. End Get
    11. End Property
    12. Public ReadOnly Property ÄnderungenÜbernehmenCommand() As ICommand
    13. Get
    14. If _änderungenÜbernehmen Is Nothing Then
    15. _änderungenÜbernehmen = New RelayCommand(Sub() ÄnderungenÜbernehmen(), Function()
    16. Return DetailsModus
    17. End Function)
    18. End If
    19. Return _änderungenÜbernehmen 'Implementierung nicht ausgeführt!
    20. End Get
    21. End Property
    22. Public Property DetailsModus() As Boolean 'Eigenschaft die im
    23. Get
    24. Return _detailsModus
    25. End Get
    26. Set
    27. [Set](_detailsModus, Value) 'Achtung! Eine NotifyPropertyChanged Implementierung wie im -Windows Template Studio-
    28. End Set
    29. End Property
    30. Private Sub NeueErstellen()
    31. DetailsModus = False
    32. End Sub


    So kann "DetailModus" in der Codebehind oder im ViewModel wiederverwendet werden, und kontrolle bei Wertänderungen aus mehreren Stellen heraus geschehen.

    Das RelayCommand habe ich aus dem GitHub Repository der WindowsTemplateStudio, nur ein wenig angepasst.

    Wenn das dann Geläufig ist mit dem RelayCommand, kann dann noch die letzte Hürde mit den "CommandParameter" angegangen werden.

    Damit kann dann im XAML ein Wert dem Command mitgegeben werden.
    Einige nutzen das um in einer ListBox die Auswahl der Elemente mitzugeben, um sie dann im Auszuwerten.

    Ich nehme Dafür immer eine eigene Eigenschaft im ViewModel um das mit den CommandParameter nicht zu nutzen.

    Freut mich das das mit den ersten Versuch gleich geklappt hat, und viel Erfolg mit den Commands.

    c.u. Joshi
    Hallo Joshi

    Genial.
    Jetzt hab ich's verstanden.

    Wenn ich den Button "immer aktiv" haben will, dann muss ich einfach

    VB.NET-Quellcode

    1. Function()
    2. 'Validierung für CanExecute
    3. Return True
    4. End Function)

    verwenden.

    Wenn nicht packe ich statt return true einen Bedingung rein.

    Habe das kurz in meinem Beispiel aktualisiert.

    Viele Grüße und Danke für die Hilfe
    Florian
    Dateien
    ----

    WebApps mit C#: Blazor
    Hallo

    In meiner RelayCommand Klasse habe ich sogar zwei Konstruktoren, wenn ein Button immer an sein soll kann man somit den Konstruktor nehmen dem man kein Prödikat mitgeben muss. Dann regelt die RelayCommand Klasse das selbst. So muss ich es nicht immer mit angeben.

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

    Ah, stimmt. Hab ich übersehen. Sorry.

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

    Ja genau so geht das...

    VB.NET-Quellcode

    1. Private _Link As String
    2. Public Property Link As String
    3. Get
    4. Return _Link
    5. End Get
    6. Set(value As String)
    7. _Link = value
    8. RaisePropertyChanged()
    9. End Set
    10. End Property
    11. Private _IsButtonActivated As Boolean
    12. Public Property IsButtonActivated As Boolean
    13. Get
    14. Return _IsButtonActivated
    15. End Get
    16. Set(value As Boolean)
    17. _IsButtonActivated = value
    18. RaisePropertyChanged()
    19. End Set
    20. End Property
    21. Private _openWebsite As ICommand
    22. Public ReadOnly Property openWebsiteCommand() As ICommand
    23. Get
    24. If _openWebsite Is Nothing Then
    25. _openWebsite = New RelayCommand(Sub() openWebsite(), Function()
    26. Return IsButtonActivated
    27. End Function)
    28. End If
    29. Return _openWebsite
    30. End Get
    31. End Property



    Oberes ist ein Auszug aus dem CommandTest.zip Anhang...

    Eine Variante wäre die Prüfung direkt in der Lambda auszwerten.

    VB.NET-Quellcode

    1. _openWebsite = New RelayCommand(Sub() openWebsite(), Function()
    2. Return Uri.IsWellFormedUriString(Link, UriKind.RelativeOrAbsolute) 'Prüfen ob der Wert in der Eigenschaft "Link" valide ist...
    3. End Function)


    Wenn im Beispiel der Checkbox "IsButtonActivated" als Eigenschaft "geBinded" wird, ist es eine gute Idee, um komplexere Mechanismen im ViewModel umzusetzen.
    Das der Button mit dem ICommand sich selbst "Enabled" oder "Disabled", ist ja der Trick.
    Ich nutzte den Element-Binding um die Checkbox mit einzuschalten.
    Meist nutze ich das um UI-Element(e) auszublenden (Visibilite=Collpased), mithilfe von BooleanToVisibilityConverter.

    Das Prinzipt scheint ja jetzt zu sitzen.

    Jetzt solltest Du zum nächstem Projekt übergehen, und ein Weltenretterprogramm schreiben, oder eine App um den Klimawandel zurückzuverwandeln... ;)

    c.u. Joshi

    Joshi schrieb:

    Das Prinzipt scheint ja jetzt zu sitzen.

    Das sehe ich genau so, nochmals vielen Dank auch beiden für eure Hilfe.

    Joshi schrieb:

    Jetzt solltest Du zum nächstem Projekt übergehen, und ein Weltenretterprogramm schreiben, oder eine App um den Klimawandel zurückzuverwandeln...

    Wenn das mal so einfach wäre, dann wäre es schön....

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    @Joshi

    Wann verwendest du die RelayCommand(Of T) ?
    Ich stehe gerade etwas auf der Leitung was den Mehrwert betrifft, würde mich aber interessieren.

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

    Hi.

    @Nofear23m
    Ich habe das nur einmal genutzt, als ich "ListBoxItems" nicht mir einer eigenen Eigenschaft für die "SelectedItems" im ViewModel brauchte.

    Ist schon lange her und ich kann mich nur wage erinnern.

    Ich glaube es war, als ich als ListBoxItems eigene UserControls definiert habe.

    Die ListBoxItems waren aus unterschiedlichen Objekten zusammengesetzt, ohne Klassenvereerbung.

    Das ist eigentlich ein Designfehler, wer aber keine andere Möglichkeit hat, kann somit das ListBoxItem in dem CommandParameter direkt übergeben.

    Eine andere Anwendung ist die CommandParameter aus anderen "ElementBindungen" direkt zu übergeben, wenn es eine reine XAML definitien ist.

    Ein Beispiel ist z.B. eine Skalierung in Form von mm, inch, km etc. die nur als Parameter für eine Umrechnung aus einem "geschloßenen" Control per XAML-Bindung mitgegeben werden muss.

    Da ich die "RelayCommand"-Datei aus dem Repository des Windows Template Studio habe, gehe ich von eienem wiederkehrendem Muster in dem Fall aus.

    Ich vertraue "sibille", und wir sollten alle von ihr adoptiert werden! ?( :S =O ;) OK, ich hab mich wieder gefangen... alles Cool, alles Cooool!

    Ein gutes Szenario wäre die Speicheroptimierung, weil mit der Parameterübergabe, via Commandparametern, können Eigenschaften und Felder im ViewModel reduziert werden.

    Ich glaube bei 3D-Objekten im View und bei echtzeit Graphen, könnte es zur Performance beitragen.

    Ansonsten kann ich keine weiteren Vermutungen mehr anstellen, weil das mit den Tatbestand einer Verschwörung einhergeht.
    SIE sind überall, die (Of T), die sind aus dem Tee gekommen und sie werden die Welt übernehmen... Mir ist schwummerich... ;)

    Nee, im ernst. Ich weiss es nicht, hier habe ich nur Copy und Paste, bei den Top-Programmiererinnen gemacht.

    Dir auch schöne Grüße, und weiter so als Moderator.

    @flori2212

    Gern geschehen.

    Eine kleine Skizze wie eine WeltenretterApp aus sehen könnte:

    Biste bereit? Ja? Also dann, Sie müssen nur den Nippel durch die Lasche ziehn... lol

    c.u Joshi
    Alter, wie geil ist das geschrieben. Ich liebe die Art von Humor.

    Ich habe wärend des lesens deiner antwort soooo doll grinsen müssen.
    Sehr geil!

    OK, das es hier und da mal einen nutzen bringen könnte habe ich mir fast gedacht, was ich jetzt so mitbekommen habe aber eher nichts was man nicht auch mit AttachedProperties oder Convertern machen könnte.

    Dachte schon ich habe irgendwo einen Denkfehler.
    Danke für das Lob bez. meiner Moderation - ich gebe mir mühe.

    In diesem Sinne - WPF Rules!

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

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

    Hallo, ich bins nochmal.

    Das mit den Commands bei Buttons klappt schon sehr gut.
    Nun will ich aber bei einer TextBox abfangen, wenn jemand die Entertaste klickt. Dies würde ich normalerweise mit Events machen, wie mache ich des aber mit Commands?

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hi.

    XML-Quellcode

    1. <TextBox>
    2. <TextBox.InputBindings>
    3. <KeyBinding Key="Enter" Command="{Binding MachWasCommand}" />
    4. </TextBox.InputBindings>
    5. </TextBox>


    Die Texteigenschaft der Textbox sollte gebunden sein im ViewModel, und dort weiterverabeitet werden.

    Weitere kapriolen bezüglich MouseOver etc. können als UI spezifisch angesehen werden, und sollten im CodeBehind gemacht werden, wenn MVVM genutzt wird.

    Zu dem können mit den InputBindings auch Appweite Tastenkombinationen realisiert werden.

    P.S. : Hier fällt die Mächtigkeit der erhabenen Commands auf. "WER DELEGIEREN WILL MUSS KOMMANDIEREN KÖNNEN! denke ich... "
    Naja, dann... Aber im ernst, die Wiederverwendung der Commands mittels RelayCommands ist Universell auch für andere "Command="{Binding ...}" Attribute verwendbar.
    Leider haben nicht alle UI/XAML elemente ein "Command"-Attribut, und eine andere Lösung muss her.

    Hoffe das hilft, und c.u. Joshi :thumbup:

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

    Joshi schrieb:

    Leider haben nicht alle UI/XAML elemente ein "Command"-Attribut, und eine andere Lösung muss her.

    Ja, leider. Aber die WPF ist ja so mächtig das man AttachedProperties nutzen kann um Command hinzuzufügen. Und wieder eine stärke der WPF.

    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:

    Zitat von Joshi: „Leider haben nicht alle UI/XAML elemente ein &quot;Command&quot;-Attribut, und eine andere Lösung muss her.“
    Ja, leider. Aber die WPF ist ja so mächtig das man AttachedProperties nutzen kann um Command hinzuzufügen. Und wieder eine stärke…


    Ist das eigentlich schwer umzusetzen? Ich habe das noch nie gebraucht... Ich meine, eigene Attached-Properties.

    "Haste mal ´n Snippet, oder ´n Euro ?", sagte der verarmte Programmierer zum Passanten. ;)


    Danke Joshi

    Joshi schrieb:

    Haste mal ´n Snippet

    Sicher, Attached Properties sind genial. Ich verwende diese z.b. um von einem UserControl aus Titel, Größe, Verhalten und DialogResults eine Window zu steuern da man in einer echten MVVM Anwendung ja im Grunde nur ein Fenster hat und Services neue Fenster wie Dialoge erstellen. So kann ich im XAML vom UserControl aus das Fenster steuern. Und sogar mit Binding.

    Mach zwar ungern ein Beispiel weil dies genau das nächste Kapitel meiner Tutorialreihe ist/wird. Aber OK.

    Angenommen ich hätte folgendes einfaches ViewModel:

    VB.NET-Quellcode

    1. Public Class DemoViewModel
    2. Inherits ViewModelBase
    3. Private _testProperty As String
    4. Public Property TestProperty As String
    5. Get
    6. Return _testProperty
    7. End Get
    8. Set(ByVal value As String)
    9. _testProperty = value
    10. RaisePropertyChanged()
    11. End Set
    12. End Property
    13. Private _testCommand As ICommand
    14. Public ReadOnly Property TestCommand As ICommand
    15. Get
    16. If _testCommand Is Nothing Then _
    17. _testCommand = New RelayCommand(AddressOf TestCommand_Execute)
    18. Return _testCommand
    19. End Get
    20. End Property
    21. Private Sub TestCommand_Execute(obj As Object)
    22. MessageBox.Show($"Du hast '{TestProperty}' eingegeben. ;-)")
    23. End Sub
    24. End Class


    Eine Klasse anlegen. z.b. MyTextBoxHelper

    VB.NET-Quellcode

    1. Public Class MyTextBoxHelper
    2. Inherits DependencyObject
    3. Public Shared Function GetEnterCommand(obj As TextBox) As ICommand
    4. Return DirectCast(obj.GetValue(EnterCommandProperty), ICommand)
    5. End Function
    6. Public Shared Sub SetEnterCommand(obj As TextBox, value As ICommand)
    7. obj.SetValue(EnterCommandProperty, value)
    8. End Sub
    9. Public Shared ReadOnly EnterCommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("EnterCommand",
    10. GetType(ICommand),
    11. GetType(MyTextBoxHelper),
    12. New UIPropertyMetadata(Nothing, AddressOf EnterCommandProperty_Changed))
    13. Private Shared Sub EnterCommandProperty_Changed(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    14. Dim tb As TextBox = TryCast(d, TextBox)
    15. If tb Is Nothing Then Throw New NullReferenceException
    16. AddHandler tb.KeyDown, Sub(sender, e2)
    17. If e2.Key = Key.Enter Then
    18. DirectCast(d.GetValue(EnterCommandProperty), ICommand).Execute(Nothing)
    19. End If
    20. End Sub
    21. End Sub
    22. End Class


    Da ein Command im View auch "nur" als DependencyProperty implementiert ist geht das Easy. Und nun kann ich auch darauf Binden.
    Man beachte in GetEnterCommand und SetEnterCommand das obj As TextBox. Gebe ich hier als Typ DependencyObject kann kann ich dieses AttachedProperty auf ALLE FrameworkElemente und Controls verwenden. Das will ich in diesem Fall nicht weshalb ich den Typ spezifiziere.

    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. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:AttachedDemo"
    7. mc:Ignorable="d"
    8. Title="MainWindow" Height="450" Width="800">
    9. <Window.DataContext>
    10. <local:DemoViewModel/>
    11. </Window.DataContext>
    12. <StackPanel>
    13. <TextBlock Text="Text in die Textbox eingeben um eine Messagebox anzuzeigen."/>
    14. <TextBox Text="{Binding TestProperty,UpdateSourceTrigger=PropertyChanged}" local:MyTextBoxHelper.EnterCommand="{Binding TestCommand}"/>
    15. </StackPanel>
    16. </Window>


    Auch Intellisense und Binding ist hier kein Problem.

    Grüße
    Sascha
    Dateien
    • AttachedDemo.zip

      (229,04 kB, 92 mal heruntergeladen, zuletzt: )
    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. ##