none
Command binding issue with using CommandReference and CommandGroup

    Question

  • I'm trying to write an app using the MVVM pattern but I'm running into an command binding issue. I'm trying to bind a button to multiple commands. To do this I'm using the CommandReference class (this is from the MVVM toolkit) and the CommandGroup class (I found this CodeProject).

    Here is a simplified version of my xaml:

      <Window.Resources>
        <c:CommandReference x:Key="TestCommand"
                  Command="{Binding Path=TestCommand}"/>
        <c:CommandGroup x:Key="TestCommandGroup">
          <c:CommandReference Command="{StaticResource TestCommand}"/>
          <c:CommandReference Command="{StaticResource TestCommand}"/>
        </c:CommandGroup>
      </Window.Resources>
      <Grid>
        <Button Content="Command Test" Command="{StaticResource TestCommandGroup}"/>
      </Grid>
    

    If I set the DataContext of the window to itself, this works just fine and the TestCommand will execute twice. The problem occurs when I want to follow the MVVM pattern and I set the DataContext of the window to a ViewModel. When I do this the button becomes disabled (I don't see any binding errors in the output window).

    I tried debugging this to see what was going on. If I set a breakpoint on the PropertyChangedCallback for the Command, I noticed the sequence this gets called is different depending on what the DataContext was set to.

    If I set the DataContext to the window, I see the following sequence:

    1. The Command for the first reference changes (setting it to the bound command)
    2. The Command for the second reference changes (setting it to the first command reference)
    3. The Command for the third reference changes (setting it to the first command reference)

    If I set the DataContext to the App class, I see the following sequence:

    1. The Command for the third reference changes (setting it to the first command reference which has a null Command)
    2. The Command for the second reference changes (setting it to the first command reference, which has a null Commnad)
    3. The Command for the first reference changes (setting it to the bound command)
    4. The Command for the third reference changes (setting it to the first command reference which now has a valid Command)
    5. The Command for the second reference changes (setting it to the first command reference which now has a valid Command)

    If anyone has a clue to what's going on here or could try debugging this I would appreciate it. To debug the two different setups I created a solution with two simple WPF apps in it. One sets the window's DataContext to itself and the other sets it to the App class. Set a break point in the CommandReference.OnCommandChanged() to notice the difference in behavior.

    You can get a zip of the solution here: 

    http://cid-d0817828920642df.office.live.com/self.aspx/CommandReference/CommandReferenceWierdness.zip

     

    Thanks,

    Nolan

    Friday, August 27, 2010 7:35 AM

Answers

  • Hi Nolan,

    I can repro your scenario with a little difference: the sequence is 2,1,3,4,5 for the second project.

    The difference between these two projects is that the DataContext is set while the window is loaded in the first project, but not in the second one. This makes the commands changes one more time after DataContext being set. And since the Command for the first CommandReference is null when window is loaded, Command changed occur only 5 times instead of 6 times.

    The reason the button is disabled seems to be that CommandReference fail to fire CanExecuteChanged event under this circumstance. A workaround to this issue is to force CommandReference to fire it's CanExecuteChanged event. You can change the ICommandMember region of CommandReference to the following code. Then everything should work fine.

        #region ICommand Members
    
        public bool CanExecute(object parameter)
        {
          
          if (this.Command != null)
            return this.Command.CanExecute(parameter);
          return false;
        }
    
        public void Execute(object parameter)
        {
          this.Command.Execute(parameter);
        }
    
        public event EventHandler CanExecuteChanged;
    
        private static void 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; 
          }
        }
    
        void OnCanExecuteChanged(object sender, EventArgs e)
        {
          
          if (this.CanExecuteChanged != null)
            this.CanExecuteChanged(this, EventArgs.Empty);
        }
    
        #endregion
    

    Hope this helps.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by Nolan Monday, September 06, 2010 10:12 PM
    Thursday, September 02, 2010 9:41 AM
  • Min,

    Thanks for your response. I get the basic idea that when steps 4 & 5 happen the UI is never notified that CanExecute has changed.

    For you solution, I see you added an OnCanExecuteChanged method to fire the CanExecuteChanged event but I don't see where you are calling this method. I'm assuming the correct place to call this in the OnCommandChanged method (let me know if there is a better location).

    So I changed my ICommand Members region to the following and everything seems to be working now (The added lines are indicated with <<---).

     

     

    #region ICommand Members
    
      public bool CanExecute(object parameter)
      {
       if (this.Command != null)
        return this.Command.CanExecute(parameter);
       return false;
      }
    
      public void Execute(object parameter)
      {
       this.Command.Execute(parameter);
      }
    
      public event EventHandler CanExecuteChanged;
    
      private static void 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;
       }
       commandReference.OnCanExecuteChanged(d, new EventArgs()); // <<<-----
      }
    
      void OnCanExecuteChanged(object sender, EventArgs e) // <<<-----
      {
       if (this.CanExecuteChanged != null)
        this.CanExecuteChanged(this, EventArgs.Empty);
      }
    
      #endregion
    

     

    Thanks,

    Nolan

    • Marked as answer by Nolan Monday, September 06, 2010 10:13 PM
    Monday, September 06, 2010 10:09 PM

All replies

  • Hi Nolan,

    After playing around with your example for a while, it comes out that it is how MainWindow.xaml is created makes things different. The initializing process  seems to be different between setting Appliaction.StartupUrl property and calling window's constructor.

    Hope this can help you a little bit.

    I will do more research as soon as I am free.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Monday, August 30, 2010 9:54 AM
  • Hi Nolan,

    I can repro your scenario with a little difference: the sequence is 2,1,3,4,5 for the second project.

    The difference between these two projects is that the DataContext is set while the window is loaded in the first project, but not in the second one. This makes the commands changes one more time after DataContext being set. And since the Command for the first CommandReference is null when window is loaded, Command changed occur only 5 times instead of 6 times.

    The reason the button is disabled seems to be that CommandReference fail to fire CanExecuteChanged event under this circumstance. A workaround to this issue is to force CommandReference to fire it's CanExecuteChanged event. You can change the ICommandMember region of CommandReference to the following code. Then everything should work fine.

        #region ICommand Members
    
        public bool CanExecute(object parameter)
        {
          
          if (this.Command != null)
            return this.Command.CanExecute(parameter);
          return false;
        }
    
        public void Execute(object parameter)
        {
          this.Command.Execute(parameter);
        }
    
        public event EventHandler CanExecuteChanged;
    
        private static void 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; 
          }
        }
    
        void OnCanExecuteChanged(object sender, EventArgs e)
        {
          
          if (this.CanExecuteChanged != null)
            this.CanExecuteChanged(this, EventArgs.Empty);
        }
    
        #endregion
    

    Hope this helps.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by Nolan Monday, September 06, 2010 10:12 PM
    Thursday, September 02, 2010 9:41 AM
  • Min,

    Thanks for your response. I get the basic idea that when steps 4 & 5 happen the UI is never notified that CanExecute has changed.

    For you solution, I see you added an OnCanExecuteChanged method to fire the CanExecuteChanged event but I don't see where you are calling this method. I'm assuming the correct place to call this in the OnCommandChanged method (let me know if there is a better location).

    So I changed my ICommand Members region to the following and everything seems to be working now (The added lines are indicated with <<---).

     

     

    #region ICommand Members
    
      public bool CanExecute(object parameter)
      {
       if (this.Command != null)
        return this.Command.CanExecute(parameter);
       return false;
      }
    
      public void Execute(object parameter)
      {
       this.Command.Execute(parameter);
      }
    
      public event EventHandler CanExecuteChanged;
    
      private static void 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;
       }
       commandReference.OnCanExecuteChanged(d, new EventArgs()); // <<<-----
      }
    
      void OnCanExecuteChanged(object sender, EventArgs e) // <<<-----
      {
       if (this.CanExecuteChanged != null)
        this.CanExecuteChanged(this, EventArgs.Empty);
      }
    
      #endregion
    

     

    Thanks,

    Nolan

    • Marked as answer by Nolan Monday, September 06, 2010 10:13 PM
    Monday, September 06, 2010 10:09 PM