Home » WPFRSS

MVVM - Refreshing buttons enable/disable state

Hi everyone... something is working wrong on my MVVM App.

I have a view with the following buttons

<StackPanel><ButtonCommand="{Binding ShowPlayersCommand}">Show Users</Button><ButtonCommand="{Binding ExitCommand}">Close</Button></StackPanel>

The idea is to show a new window when "Show Users" is pressed, then disable that button.

When this new window is closed, the button should be enabled again.

 

I'm trying to use DelegateCommand to implement that behavior.

 

My ViewModel has:

  public class MainWindowViewModel : IMainWindowModel, INotifyPropertyChanged
  {
    private PlayerView mView;
    private bool mPlayerViewIsVisible;

    public MainWindowViewModel()
    {
      ShowPlayersCommand = new DelegateCommand(ShowPlayers, CanShowPlayers);
    }

    private void ShowPlayers(object param)
    {
      if (!PlayerViewIsVisible)
      {
        mView = new PlayerView();
        mView.DataContext = new PlayerViewModel();
        mView.Show();
        mView.Closing += new CancelEventHandler(mView_Closing);
        PlayerViewIsVisible = true;
      }
    }

    void mView_Closing(object sender, CancelEventArgs e)
    {
      PlayerViewIsVisible = false;
    }

    private bool CanShowPlayers(object param)
    {
      return !PlayerViewIsVisible;
    }

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

    #region IMainWindowModel Members

    public ICommand ShowPlayersCommand { get; set; }

    public bool PlayerViewIsVisible
    {
      get
      {
        return mPlayerViewIsVisible;
      }
      set
      {
        mPlayerViewIsVisible = value;
        OnPropertyChanged("PlayerViewIsVisible");
      }
    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
  }

But it appears that my button is not being refreshed on the right time.

When I press "Show Users", the new window is shown but the button is not being disabled. If I try to press the button again, it becomes disabled and remains as it after I close the new window.

I have noticed the method CanShowPlayers is called when I press the button (this is what I expect), I thought the button should be notified to disable on this step but it is not happening.

I also have noticed the method CanShowPlayers is not being called when I close the window... and thinking about it... where on the code I'm telling to do that? -lol-

Somebody can help me to understand what I'm doing wrong?

 

 

8 Answers Found

 

Answer 1

Try this:

privatevoid ShowPlayers(object param)
  {
   if (!PlayerViewIsVisible)
   {
    mView = new PlayerView();
    mView.DataContext = new PlayerViewModel();
    mView.Show();
    mView.Closing += new CancelEventHandler(mView_Closing);
    PlayerViewIsVisible = true;

    // Force the command to recheck:
    CommandManager.InvalidateRequerySuggested();
   }
  }
By calling InvalidateRequerySuggested, you can tell WPF that it needs to "recheck" your ICommand implementations to see if they're still enabled.

 

Answer 2

Thanks for your suggestion Reed. I has sense, but I've tried it and it is not working

 

 

 

Answer 3

Trying to isolate the problem, I have simplified my ModelView:

 

 

publicclass MainWindowViewModel : IMainWindowModel, INotifyPropertyChanged
  {
    private PlayerView mPlayerView;

    public MainWindowViewModel()
    {
      ShowPlayersCommand = new DelegateCommand(ShowPlayers, CanShowPlayers);
      ExitCommand = new DelegateCommand(Exit, CanExit);
    }

    privatevoid ShowPlayers(object param)
    {
      PlayerView = new PlayerView();
      PlayerView.DataContext = new PlayerViewModel();
      PlayerView.Show();
    }

    privatebool CanShowPlayers(object param)
    {
      return PlayerView == null;
    }

    privatevoid Exit(object param)
    {
      if (PlayerView != null)
      {
        PlayerView.Close();
      }
      Application.Current.Shutdown();
    }

    privatebool CanExit(object param)
    {
      returntrue;
    }

    privatevoid OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
    }

    #region IMainWindowModel Members

    public ICommand ShowPlayersCommand { get; set; }
    public ICommand ExitCommand { get; set; }

    public PlayerView PlayerView
    {
      get
      {
        return mPlayerView;
      }
      set
      {
        mPlayerView = value;
        OnPropertyChanged("View");
      }
    }

    #endregion#region INotifyPropertyChanged Members

    publicevent PropertyChangedEventHandler PropertyChanged;

    #endregion
  }

 

I create and open a new window  on the method ShowPlayers.

I put return PlayerView == null; on CanShowPlayers, so the button  will become enabled  only if the view  is null.

It means that, whenever I click on "Show Users" button, the PlayerView property changes from null to a certain value, and the button should become disabled. This is not happening: the button remains enabled.

When I try to press the button again, it becomes disabled. And there is no way to enable it again (which should happend when I close  the PlayerView).

I have follow many samples out there... What I am missing? You think I should post my Interface or DelegateCommand implementation? Do I need a Service Pack or something?

I have also tried:

privatevoid ShowPlayers(object param)
    {
      PlayerView = new PlayerView();
      PlayerView.DataContext = new PlayerViewModel();
      PlayerView.Show();
      CommandManager.InvalidateRequerySuggested();
    }

But still not working!

 

Answer 4

Hi,

Bind ShowUsers Button's IsEnabled Property with PlayerViewIsVisible.

If it still not worked, then use View.UpdateLayout()

Thanks,

Rajnikant


 

Answer 5

I'm sure it will work as you suggest, but, in that case, why to have a CanShowPlayers method if it will not work accurately?

I vote your suggestion as helpful, but I'll give time to others to respond, since the CanXXXX() methods are mentioned as a solution in many sites and for now I'm experienced that those are not working. Maybe a bug or a needed Update?

Thank you so much

 

Answer 6

I did a sample app  based on what you were saying that acts exactly as you describe and that follows the MVVM pattern.  I think your issue is the 

 

privatebool CanShowPlayers(object param)
  {
   return PlayerView == null;
  }
because your not passing in a parameter.  However, I'm not sure about that so here is my sample code.

I used the MVVM Toolkit to create the basics of my project.  You can find it here: http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit

I created Model-View-ViewModel project called ButtonCommandSample.

The MainViewModel class  thats generated by default, I changed to look like the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;

using ButtonCommandSample.Commands;
using ButtonCommandSample.Views;

namespace ButtonCommandSample.ViewModels
{
  publicclass MainViewModel : ViewModelBase
  {
    private DelegateCommand exitCommand;
    private DelegateCommand showPlayersCommand;

    bool playerWindowShowing = false;
    publicbool PlayerWindowShowing
    {
      get { return playerWindowShowing; }
      set 
      { 
        playerWindowShowing = value;
        OnPropertyChanged("PlayerWindowShowing");
      }
    }
    public ICommand ShowPlayersCommand
    {
      get
      {
        if(showPlayersCommand == null)
        {
          showPlayersCommand = new DelegateCommand(ShowPlayers, CanShowPlayers);
        }
        return showPlayersCommand;
      }
    }

    void ShowPlayers()
    {
      if(!PlayerWindowShowing)
      {
        PlayerWindowShowing = true;
        PlayerWindow pw = new PlayerWindow();
        pw.Closed += new EventHandler(pw_Closed);
        pw.Show();
        
      }
    }

    void pw_Closed(object sender, EventArgs e)
    {
      PlayerWindowShowing = false;
    }

    bool CanShowPlayers()
    {
      return !playerWindowShowing;
    }

    #region Constructor

    public MainViewModel()
    {
      // Blank
    }

    #endregionpublic ICommand ExitCommand
    {
      get
      {
        if (exitCommand == null)
        {
          exitCommand = new DelegateCommand(Exit);
        }
        return exitCommand;
      }
    }

    privatevoid Exit()
    {
      Application.Current.Shutdown();
    }
  }
}

The MainView I changed to the following:

<Window x:Class="ButtonCommandSample.Views.MainView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:c="clr-namespace:ButtonCommandSample.Commands"
  Title="Main Window" Height="400" Width="800">
  
  <Window.Resources>
    <!-- Allows a KeyBinding to be associated with a command defined in the View Model -->
    <c:CommandReference x:Key="ExitCommandReference" Command="{Binding ExitCommand}" />
  </Window.Resources>
  
  <Window.InputBindings>
    <KeyBinding Key="X" Modifiers="Control" Command="{StaticResource ExitCommandReference}" /> 
  </Window.InputBindings>
  
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="_File">
        <MenuItem Command="{Binding ExitCommand}" Header="E_xit" InputGestureText="Ctrl-X" />
      </MenuItem>
    </Menu>
	
    <Grid>
      <!-- Add additional content here -->
      <Grid.RowDefinitions>
        <RowDefinition Height=".9*"/>
        <RowDefinition Height=".1*"/>
      </Grid.RowDefinitions>
      <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center">
        <Button Content="Show Players" Command="{Binding ShowPlayersCommand}"/>
        <Button Content="Other command here" Margin="5,0,0,0" />
      </StackPanel>            
    </Grid>
  </DockPanel>
</Window>
The PlayerWindow was just a new window.  I did not add anything into it as I didn't need to to show  you how this is supposed to work.


Hope this helps,

Mark


 

 

Answer 7

A lot of thanks Mark. I could not test it, because we are developing without the MVVM Toolkit. I'll accept your answer, since it worked for you and will help other ppl too.

I tried to modify my DelegateCommand class  in order to suppress the parameters, but it was being a pain.

Finally, I found the problem origins: the CanExecuteChanged event was not being fired. I consider this a bug.

The solution to the problem was to modify my DelegateCommand class as follows:

publicclass DelegateCommand : ICommand
  {
    Func<object, bool> canExecute;
    Action<object> executeAction;

    public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
    {
      this.executeAction = executeAction;
      this.canExecute = canExecute;
      //this.CanExecuteChanged += new EventHandler(DelegateCommand_CanExecuteChanged);
    }

    //void DelegateCommand_CanExecuteChanged(object sender, EventArgs e)//{//  CanExecuteChanged(this, e);//}#region ICommand Members

    publicbool CanExecute(object parameter)
    {
      return canExecute(parameter);
    }

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

    publicvoid Execute(object parameter)
    {
      executeAction(parameter);
    }

    #endregion
  }

The key was to define CanExecuteChanged as follows:

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

In fact, so tricky and obscure, but I hope this helps somebody else.

 

Thank you

 

Answer 8

Glad you found the culprit, but just to give you a bit more and help whoever else might stumble on this:

The toolkit essentially just creates a project type template in visual studio that has some classes already defined.  

It creates a project laid out like the following:

Folder Commands

This folder contains the DelegateCommand and CommandReference cs files.

Empty folder Models

Folder ViewModels

ViewModelBase and MainViewModel cs files. Very generic mvvm  classes.

Folder Views

MainView xaml window  using the MainViewModel

 

Thats basically all the toolkit provides. An easy way to create a starting point without having to do all that grunt work every time.  As your not using it, you can simply utilize my previous sample with the following 2 classes:  

DelegateCommand.cs:

 

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

namespace <span style="font-family:Verdana,Arial,Helvetica,sans-serif">ButtonCommandSample</span>.Commands<br/>{
  ///<summary>///   This class  allows delegating the commanding logic to methods passed as parameters,///   and enables a view  to bind commands to objects that are not part of the element tree.///</summary>publicclass DelegateCommand : ICommand
  {
    #region Constructors

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action executeMethod)
      : this(executeMethod, null, false)
    {
    }

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
      : this(executeMethod, canExecuteMethod, false)
    {
    }

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
      if (executeMethod == null)
      {
        thrownew ArgumentNullException("executeMethod");
      }

      _executeMethod = executeMethod;
      _canExecuteMethod = canExecuteMethod;
      _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    }

    #endregion#region public  Methods

    ///<summary>///   Method to determine if the command can be executed///</summary>publicbool CanExecute()
    {
      if (_canExecuteMethod != null)
      {
        return _canExecuteMethod();
      }
      returntrue;
    }

    ///<summary>///   Execution of the command///</summary>publicvoid Execute()
    {
      if (_executeMethod != null)
      {
        _executeMethod();
      }
    }

    ///<summary>///   Property to enable or disable  CommandManager's automatic requery on this command///</summary>publicbool IsAutomaticRequeryDisabled
    {
      get
      {
        return _isAutomaticRequeryDisabled;
      }
      set
      {
        if (_isAutomaticRequeryDisabled != value)
        {
          if (value)
          {
            CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
          }
          else
          {
            CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
          }
          _isAutomaticRequeryDisabled = value;
        }
      }
    }

    ///<summary>///   Raises the CanExecuteChaged event///</summary>publicvoid RaiseCanExecuteChanged()
    {
      OnCanExecuteChanged();
    }

    ///<summary>///   Protected virtual method to raise CanExecuteChanged event///</summary>protectedvirtualvoid OnCanExecuteChanged()
    {
      CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    #endregion#region ICommand Members

    ///<summary>///   ICommand.CanExecuteChanged implementation///</summary>publicevent EventHandler CanExecuteChanged
    {
      add
      {
        if (!_isAutomaticRequeryDisabled)
        {
          CommandManager.RequerySuggested += value;
        }
        CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
      }
      remove
      {
        if (!_isAutomaticRequeryDisabled)
        {
          CommandManager.RequerySuggested -= value;
        }
        CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
      }
    }

    bool ICommand.CanExecute(object parameter)
    {
      return CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
      Execute();
    }

    #endregion#region Data

    privatereadonly Action _executeMethod = null;
    privatereadonly Func<bool> _canExecuteMethod = null;
    privatebool _isAutomaticRequeryDisabled = false;
    private List<WeakReference> _canExecuteChangedHandlers;

    #endregion
  }

  ///<summary>///   This class allows delegating the commanding logic to methods passed as parameters,///   and enables a View to bind commands to objects that are not part of the element tree.///</summary>///<typeparam name="T">Type of the parameter passed to the delegates</typeparam>publicclass DelegateCommand<T> : ICommand
  {
    #region Constructors

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action<T> executeMethod)
      : this(executeMethod, null, false)
    {
    }

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
      : this(executeMethod, canExecuteMethod, false)
    {
    }

    ///<summary>///   Constructor///</summary>public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
      if (executeMethod == null)
      {
        thrownew ArgumentNullException("executeMethod");
      }

      _executeMethod = executeMethod;
      _canExecuteMethod = canExecuteMethod;
      _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    }

    #endregion#region Public Methods

    ///<summary>///   Method to determine if the command can be executed///</summary>publicbool CanExecute(T parameter)
    {
      if (_canExecuteMethod != null)
      {
        return _canExecuteMethod(parameter);
      }
      returntrue;
    }

    ///<summary>///   Execution of the command///</summary>publicvoid Execute(T parameter)
    {
      if (_executeMethod != null)
      {
        _executeMethod(parameter);
      }
    }

    ///<summary>///   Raises the CanExecuteChaged event///</summary>publicvoid RaiseCanExecuteChanged()
    {
      OnCanExecuteChanged();
    }

    ///<summary>///   Protected virtual method to raise CanExecuteChanged event///</summary>protectedvirtualvoid OnCanExecuteChanged()
    {
      CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    ///<summary>///   Property to enable or disable CommandManager's automatic requery on this command///</summary>publicbool IsAutomaticRequeryDisabled
    {
      get
      {
        return _isAutomaticRequeryDisabled;
      }
      set
      {
        if (_isAutomaticRequeryDisabled != value)
        {
          if (value)
          {
            CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
          }
          else
          {
            CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
          }
          _isAutomaticRequeryDisabled = value;
        }
      }
    }

    #endregion#region ICommand Members

    ///<summary>///   ICommand.CanExecuteChanged implementation///</summary>publicevent EventHandler CanExecuteChanged
    {
      add
      {
        if (!_isAutomaticRequeryDisabled)
        {
          CommandManager.RequerySuggested += value;
        }
        CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
      }
      remove
      {
        if (!_isAutomaticRequeryDisabled)
        {
          CommandManager.RequerySuggested -= value;
        }
        CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
      }
    }

    bool ICommand.CanExecute(object parameter)
    {
      // if T is of value type and the parameter is not// set yet, then return false if CanExecute delegate// exists, else return trueif (parameter == null &&
        typeof(T).IsValueType)
      {
        return (_canExecuteMethod == null);
      }
      return CanExecute((T)parameter);
    }

    void ICommand.Execute(object parameter)
    {
      Execute((T)parameter);
    }

    #endregion#region Data

    privatereadonly Action<T> _executeMethod = null;
    privatereadonly Func<T, bool> _canExecuteMethod = null;
    privatebool _isAutomaticRequeryDisabled = false;
    private List<WeakReference> _canExecuteChangedHandlers;

    #endregion
  }

  ///<summary>///   This class contains methods for the CommandManager that help avoid memory leaks by///   using weak references.///</summary>internalclass CommandManagerHelper
  {
    internalstaticvoid CallWeakReferenceHandlers(List<WeakReference> handlers)
    {
      if (handlers != null)
      {
        // Take a snapshot of the handlers before we call out to them since the handlers// could cause the array to me modified while we are reading it.

        EventHandler[] callees = new EventHandler[handlers.Count];
        int count = 0;

        for (int i = handlers.Count - 1; i >= 0; i--)
        {
          WeakReference reference = handlers[i];
          EventHandler handler = reference.Target as EventHandler;
          if (handler == null)
          {
            // Clean up old handlers that have been collected
            handlers.RemoveAt(i);
          }
          else
          {
            callees[count] = handler;
            count++;
          }
        }

        // Call the handlers that we snapshottedfor (int i = 0; i < count; i++)
        {
          EventHandler handler = callees[i];
          handler(null, EventArgs.Empty);
        }
      }
    }

    internalstaticvoid AddHandlersToRequerySuggested(List<WeakReference> handlers)
    {
      if (handlers != null)
      {
        foreach (WeakReference handlerRef in handlers)
        {
          EventHandler handler = handlerRef.Target as EventHandler;
          if (handler != null)
          {
            CommandManager.RequerySuggested += handler;
          }
        }
      }
    }

    internalstaticvoid RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
    {
      if (handlers != null)
      {
        foreach (WeakReference handlerRef in handlers)
        {
          EventHandler handler = handlerRef.Target as EventHandler;
          if (handler != null)
          {
            CommandManager.RequerySuggested -= handler;
          }
        }
      }
    }

    internalstaticvoid AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
    {
      AddWeakReferenceHandler(ref handlers, handler, -1);
    }

    internalstaticvoid AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
    {
      if (handlers == null)
      {
        handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
      }

      handlers.Add(new WeakReference(handler));
    }

    internalstaticvoid RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
    {
      if (handlers != null)
      {
        for (int i = handlers.Count - 1; i >= 0; i--)
        {
          WeakReference reference = handlers[i];
          EventHandler existingHandler = reference.Target as EventHandler;
          if ((existingHandler == null) || (existingHandler == handler))
          {
            // Clean up old handlers that have been collected// in addition to the handler that is to be removed.
            handlers.RemoveAt(i);
          }
        }
      }
    }
  }
}

 

CommandReference.cs:

 

using System;
using System.Windows;
using System.Windows.Input;

namespace <span style="font-family:Verdana,Arial,Helvetica,sans-serif">ButtonCommandSample</span>.Commands<br/>{
  ///<summary>/// This class facilitates associating a key binding in XAML markup to a command/// defined in a View Model by exposing a Command dependency property./// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.///</summary>publicclass CommandReference : Freezable, ICommand
  {
    public CommandReference()
    {
      // Blank
    }

    publicstaticreadonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));

    public ICommand Command
    {
      get { return (ICommand)GetValue(CommandProperty); }
      set { SetValue(CommandProperty, value); }
    }

    #region ICommand Members

    publicbool CanExecute(object parameter)
    {
      if (Command != null)
        return Command.CanExecute(parameter);
      returnfalse;
    }

    publicvoid Execute(object parameter)
    {
      Command.Execute(parameter);
    }

    publicevent EventHandler CanExecuteChanged;

    privatestaticvoid OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      CommandReference commandReference = d as CommandReference;
      ICommand oldCommand = e.OldValue as ICommand;
      ICommand newCommand = e.NewValue as ICommand;

      if (oldCommand != null)
      {
        oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
      }
      if (newCommand != null)
      {
        newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
      }
    }

    #endregion#region Freezable

    protectedoverride Freezable CreateInstanceCore()
    {
      thrownew NotImplementedException();
    }

    #endregion
  }
}

Regards,

Mark

 

 
 
 

<< Previous      Next >>


Microsoft   |   Windows   |   Visual Studio   |   Follow us on Twitter