none
Beginnerfrage MVVM: Commands, CanExecute RRS feed

  • Frage

  • Ich habe ein Login. Die Login-Taste ist an das Command "Login", welches von ICommand erbt, gebunden. Je nachdem, ob der User ausgewählt wurde (User im (View)Model != null) oder nicht (User =null), wird die Taste zum einloggen automatisch aktiviert oder nicht.

    Das Login funktioniert, solange ich in der CanExecute-Methode einfach den Wert true zurück gebe. Sobald ich aber den obigen Sachverhalt implementieren möchte, funktioniert es nicht mehr. Als Parameter übergebe ich den User, welcher im ViewModel den User des Models wrappt.

    public bool CanExecute(object parameter)
        {
          return (parameter is User && parameter != null);
        }
    

    Die Lösung liegt wohl im CanExecuteChange-Event. Mir ist aber unklar, wie ich diesen implementieren, und vor allem aufrufen muss. Kann mir jemand weiterhelfen bzw. Links zu ähnlichen Bsp. angeben? Ich werde nicht fündig...

    Mittwoch, 6. April 2011 08:15

Antworten


  • Was mir auffällt: Wenn ich in CanExecute einen Breakpoint setze komme ich nur beim Aufstarten des Programms rein. Nachher nicht mehr.

    CanExecute wird ohne weiteres Zutun nur beim Laden des Elements aufgerufen,
    das den Command bindet.
    Danach nur wenn CanExecuteChanged gefeuert wird.
    Das scheint bei Dir evtl. bislang nicht zu passieren.

    Man kann's bequem lösen und CanExecuteChanged mittels eines
    Timers pulsierend auslösen. Die UI ist dann im Endeffekt ausreichend synchron.
    Ihmo läuft das mit den Routed(UI)Commands partiell so ab (außerdem werden noch Input-Aktionen
    berücksichtigt)

    Man kann CanExecuteChanged alternativ auch jeweils dann feuern, wenn sich
    eine relevante Bedingung ändert, die in CanExecute ausgewertet wird.
    Das ist ein bisschen aufwendiger.

    Du müsstest CanExecuteChanged z.B. dann feuern, wenn sich der
    CommandParameter 'MyUser' aus Deinem VM ändert.


    Christoph

    //LoginViewModel.cs
    class LoginViewModel
    {
    
     public LoginViewModel() {LoginCommand = new LoginCommand(); }
    
     private User _myUser ;
     public User MyUser 
     { 
      get { return _myUser;}
      set
      {
       if (value == _myUser) return;
       _myUser = value; 
       if (null != LoginCommand)
        LoginCommand.FireCanExecuteChanged();
      }
     }
     public LoginCommand LoginCommand
     {
      get;
      set;
     }
    }
    
    //LoginCommand.cs
    class LoginCommand : ICommand
    {
    
     public bool CanExecute(object parameter)
     {
      return (parameter is User && parameter != null);
     }
    
     public void Execute(object parameter)
     {
      MyModel.LoginUser((string) parameter);
     }
    
     public event EventHandler CanExecuteChanged;
    
     public void FireCanExecuteChanged()
     {
      if (CanExecuteChanged != null)
       CanExecuteChanged(this, EventArgs.Empty);
     }
    }
    



    • Als Antwort markiert Code4132 Donnerstag, 7. April 2011 08:20
    Donnerstag, 7. April 2011 02:40

Alle Antworten

  • Bist du sicher das der übergebene Parameter vom Type user ist?

    CanExecute kann man auf mehrere Wege aufrufen. Am einfachsten ist es mit einem Timer:

    Schau mal hier:

    http://terreaux.wordpress.com/2009/08/10/wpf-canexecute-refreshed/

     

    Mittwoch, 6. April 2011 08:53
    Beantworter
  • Danke für den Link, werde ich gleich ansehen.

    Ja ich denke, dass der Parameter korrekt ist:

     <s:SurfaceButton x:Name="LoginButton" Content="Login" IsEnabled="False" Command="{Binding Path=LoginCommand}" 
            CommandParameter="{Binding Path=MyUser}" />
    (Der DataContext wird im code behind gesetzt.)

    Was mir auffällt: Wenn ich in CanExecute einen Breakpoint setze komme ich nur beim Aufstarten des Programms rein. Nachher nicht mehr.

    Mittwoch, 6. April 2011 09:03
  • Dann wird das CanExecute nicht mehr aufgerufen.
    Bau mal den Timer ein...
    Mittwoch, 6. April 2011 09:14
    Beantworter
  • Oder muss man das CanExecute auch binden?

     Edit:

    Ich habe es nun ohne Timer versucht. An der Stelle, wo ein User ausgewählt wird, wird "CommandManager.InvalidateRequerySuggester()" aufgerufen. Trotzdem komme ich nicht zum Breakpoint.

    Mittwoch, 6. April 2011 09:17
  • Du brauchst nur den Command im Xaml binden. Im Codebehind oder ViewModel hast du ja ein Command definiert.
    Benutzt ein RelayCommand?

    http://geekswithblogs.net/lbugnion/archive/2009/09/26/using-relaycommands-in-silverlight-and-wpf.aspx

     

    Mittwoch, 6. April 2011 09:43
    Beantworter
  • Bis dahin mal vielen Dank für die Hilfe.

    Ehrlich gesagt, blicke ich bei den RelayCommands nicht ganz durch (habe den Artikel gelesen bzw. noch gegooglet).

    Ich verwende (wenn ich mit den Beispielen vergleiche) kein RelayCommand, sondern eine eigene Klasse, die ich von ICommand abgeleitet habe:

     class LoginCommand : ICommand
      {
        public bool CanExecute(object parameter)
        {
          return (parameter is User && parameter != null);
        }
    
        public void Execute(object parameter)
        {
          MyModel.LoginUser((string) parameter);
        }
    
        public event EventHandler CanExecuteChanged;
      }

    Im ViewModel instanziere ich diese Klasse und speichere sie in einer Property ab:

        public ICommand LoginCommand
        { get; set; }
    
        public LoginViewModel() 
        {
          LoginCommand = new LoginCommand();
          ... 
        }

    Mittwoch, 6. April 2011 11:08

  • Was mir auffällt: Wenn ich in CanExecute einen Breakpoint setze komme ich nur beim Aufstarten des Programms rein. Nachher nicht mehr.

    CanExecute wird ohne weiteres Zutun nur beim Laden des Elements aufgerufen,
    das den Command bindet.
    Danach nur wenn CanExecuteChanged gefeuert wird.
    Das scheint bei Dir evtl. bislang nicht zu passieren.

    Man kann's bequem lösen und CanExecuteChanged mittels eines
    Timers pulsierend auslösen. Die UI ist dann im Endeffekt ausreichend synchron.
    Ihmo läuft das mit den Routed(UI)Commands partiell so ab (außerdem werden noch Input-Aktionen
    berücksichtigt)

    Man kann CanExecuteChanged alternativ auch jeweils dann feuern, wenn sich
    eine relevante Bedingung ändert, die in CanExecute ausgewertet wird.
    Das ist ein bisschen aufwendiger.

    Du müsstest CanExecuteChanged z.B. dann feuern, wenn sich der
    CommandParameter 'MyUser' aus Deinem VM ändert.


    Christoph

    //LoginViewModel.cs
    class LoginViewModel
    {
    
     public LoginViewModel() {LoginCommand = new LoginCommand(); }
    
     private User _myUser ;
     public User MyUser 
     { 
      get { return _myUser;}
      set
      {
       if (value == _myUser) return;
       _myUser = value; 
       if (null != LoginCommand)
        LoginCommand.FireCanExecuteChanged();
      }
     }
     public LoginCommand LoginCommand
     {
      get;
      set;
     }
    }
    
    //LoginCommand.cs
    class LoginCommand : ICommand
    {
    
     public bool CanExecute(object parameter)
     {
      return (parameter is User && parameter != null);
     }
    
     public void Execute(object parameter)
     {
      MyModel.LoginUser((string) parameter);
     }
    
     public event EventHandler CanExecuteChanged;
    
     public void FireCanExecuteChanged()
     {
      if (CanExecuteChanged != null)
       CanExecuteChanged(this, EventArgs.Empty);
     }
    }
    



    • Als Antwort markiert Code4132 Donnerstag, 7. April 2011 08:20
    Donnerstag, 7. April 2011 02:40
  • Danke für die ausführliche Antwort und Erklärung.
    Donnerstag, 7. April 2011 08:40