Laden...

Frage zum RelayCommand

Erstellt von ErfinderDesRades vor 11 Jahren Letzter Beitrag vor 11 Jahren 3.899 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 11 Jahren
Frage zum RelayCommand

Habe mir von Josh Smith (THE MODEL-VIEW-VIEWMODEL (MVVM) DESIGN PATTERN FOR WPF) Demo-Code gezogen, der eine Implementation eines RelayCommands enthält. Und von Lauriel Bugnon verwende ich das WpfToolkit, welches ein ebensolches RelayCommand enthält.
RelayCommand implementiert ICommand, und beide Autoren verwenden ein Custom Event für den Schnittstellenmember ICommand.CanExecuteChanged, und zwar in dieser Weise:


      public event EventHandler CanExecuteChanged {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }

Also wer das Event abonniert wird einfach umgeleitet auf das statische Event CommandManager.RequerySuggested.

Aber das ist doch Unfug!
Nun wird der Abonnent doch nicht informiert, wenn sich CanExecuted seines RelayCommands verändert, sondern er wird immer informiert, wenn irgendein CommandManager die static Requery abfährt, mit der das CanExecute aller möglichen Commands eruiert wird.

Wie gesagt: Hätte ich diesen "Unfug" nur bei einem der beiden Autoren gefunden, würde ich denken "irren ist menschlich". Aber weils beide so machen, wollte ich mal fragen, ob mir das einer erklären kann?

Der frühe Apfel fängt den Wurm.

5.742 Beiträge seit 2007
vor 11 Jahren

Aber das ist doch Unfug!

Nicht unbedingt:

Nun wird der Abonnent doch nicht informiert, wenn sich CanExecuted seines RelayCommands verändert, sondern er wird immer informiert, wenn irgendein CommandManager die static Requery abfährt, mit der das CanExecute aller möglichen Commands eruiert wird

...und das ist z.B. auch der Fall, wenn sich eine gebundene Property ändert usw.
Ohne diese Vorgehensweise müsste man sich komplett selbst um die Auslösung des Events kümmern - das kann man zwar machen, ist aber ein erheblicher Mehraufwand.

Den kann man sich (mit vertretbaren Kosten der Performance) aber leicht sparen.

Probiere es testweise mal mit einem kleinen Beispiel, in dem du eine Textbox und ein Button an ein ViewModel bindest und CanExecute des Commands vom Button prüfen lässt, ob der Text nicht leer ist.
Dann ändere den Text und du siehst die Auswirkungen dieser Implementierung.

1.378 Beiträge seit 2006
vor 11 Jahren

Ich verwende Commands mit der gleichen Implementierung aber ich verwende die CanExecute Methode bzw. das Event eigentlich nie - Ich verwende öfters eigene Properties, die dann über StyleTrigger evt. etwas disablen.

Ich finde die Implementierung mittels CommandManager.RequerySuggested auch eher unschön:
Es wird dann ja quasi für jeden Command der gerade in einer View steckt bei jeder Änderung die die View bemerkt ein CanExecuteChanged erzeugt oder? Zusätzlich bekommt der Command nichts davon mit, wenn sich dessen Zustand im Hintergrund geändert haben sollte.

Lg, XXX

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 11 Jahren

Einen Test habe ich bereits:

Ich habe einen Treeview mit verschiedenerlei TreeItems drinne, und als CanExecute ist definiert, dass der ItemTyp getestet wird, und nur bei bestimmten ItemTypen soll das ContextMenüItem-Command ausführen können.

Das Event Funktioniert definitiv falsch, wenn ich dieser Implementation folge.
Also die Ausführbarkeit funktioniert richtig, aber das Event wird ganz beliebig geworfen, und dann auch noch "lügend", also wird auch geworfen, obwohl sich garnix geändert hat.
Und wird nicht ausgelöst, wenn sich die Ausführbarkeit ändert.

Ich denke auch, dass man sich tatsächlich selbst drum kümmern muß - ebenso wie man sich ums richtige werfen des PropertyChanged-Events kümmern muß, wenn man INotifyPropertyChanged implementiert.

Ist ja auch kein Act sich selbst drum zu kümmern, weil man hat ja im RelayCommand Kontrolle über die CanExecute-Methode.

(@xxxprod: Das Event habich auch noch nie genutzt, aber wo ichs RelayCommand selbst schreibe, möchtichs doch richtig haben.)

Der frühe Apfel fängt den Wurm.

1.378 Beiträge seit 2006
vor 11 Jahren

Aus gegebenen Anlass in Command setzt Button nicht auf disabled, hab ich mir das Thema noch einmal genauer angesehen und bin eigentlich entsetzt wie falsch eine solche Implementierung eigentlich ist und doch verwendet sie allerwelt weil sie vermutlich bequem ist (auch ich selbst bis jetzt noch).

Hier ein Beispiel, dass die Problematik schön veranschaulicht wie ich finde:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <TabControl ItemsSource="{Binding MyCommands}">
      
      <TabControl.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Key}"/>
        </DataTemplate>
      </TabControl.ItemTemplate>
      
      <TabControl.ContentTemplate>
        <DataTemplate>
          <ItemsControl ItemsSource="{Binding Value}">
            <ItemsControl.ItemTemplate>
              <DataTemplate>
                <Button Command="{Binding}" Content="{Binding Name}"/>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>
        </DataTemplate>
      </TabControl.ContentTemplate>
      
    </TabControl>
  </StackPanel>
</Window>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;

public partial class MainWindow
{
    public Dictionary<string, ICommand[]> MyCommands { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        MyCommands = new Dictionary<string, ICommand[]>();

        var commands = new ICommand[10];
        for (int j = 0; j < commands.Length; j++)
        {
            commands[j] = new MyCommandImpl();
        }
        MyCommands.Add("RequerySuggested", commands);

        var commands2 = new ICommand[10];
        for (int j = 0; j < commands2.Length; j++)
        {
            commands2[j] = new MyCommandImpl2();
        }
        MyCommands.Add("RaiseCanExecuteChanged", commands2);

        DataContext = this;
    }
}
public class MyCommandImpl : ICommand, INotifyPropertyChanged
{
    private int _clicked, _checked;
    private string _name;

    public string Name { get { return _name; } private set { _name = value; OnPropertyChanged("Name"); } }

    public void Execute(object parameter) { _clicked++; }

    public bool CanExecute(object parameter)
    {
        Name = ++_checked + " times checked";
        return _clicked < 3;
    }

    event EventHandler ICommand.CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
public class MyCommandImpl2 : ICommand, INotifyPropertyChanged
{
    private int _clicked, _checked;
    private string _name;

    public string Name { get { return _name; } private set { _name = value; OnPropertyChanged("Name"); } }

    public void Execute(object parameter) { _clicked++; RaiseCanExecuteChanged(null); }

    public bool CanExecute(object parameter)
    {
        Name = ++_checked + " times checked";
        return _clicked < 3;
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged(EventArgs e) { if (CanExecuteChanged != null) CanExecuteChanged(this, e); }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}

//kurze Überlegung dazu: Hätte man in einer Applikation wie Mircosoft Word, die potentiell über 1000 Menüitems haben kann so eine Implementierung, die Applikation würde vermutlich nur damit beschäftigt sein deren CanExecute Status auszuwerten.