开发者

WPF: using Commands bound in a UserControl

开发者 https://www.devze.com 2023-03-02 15:20 出处:网络
I\'m doing a sample with MVVM and have a problem with commands. I have an Article class (with ID, Name, Price, etc.), an ArticleViewModel that represents the view model, and a user control (ArticleCon

I'm doing a sample with MVVM and have a problem with commands. I have an Article class (with ID, Name, Price, etc.), an ArticleViewModel that represents the view model, and a user control (ArticleControl) that allows to input the data for the article, with bindings to the properties of the ArticleViewModel. This user control has a biding for a save command.

   <UserControl.CommandBindings>
      <CommandBinding x:Name="saveCmd" 
                      Command="local:Commands.Save" 
                      CanExecute="CommandBinding_CanExecute"
                      Executed="CommandBinding_Executed"/>
   </UserControl.CommandBindings>

This is how the command is defined:

   public class Commands
   {
      private static Route开发者_运维知识库dUICommand _save;
      public static RoutedUICommand Save
      {
         get { return _save; }
      }

      static Commands()
      {
         InputGestureCollection saveInputs = new InputGestureCollection();
         saveInputs.Add(new KeyGesture(Key.S, ModifierKeys.Control, "Ctrl+S"));

         _save = new RoutedUICommand(
            "Save",
            "Save",
            typeof(Commands),
            saveInputs);
      }
   }

And the command binding handlers:

  private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
     double baseprice = 0;
     double.TryParse(ArticleBasePrice.Text, out baseprice);

     e.CanExecute =
        !string.IsNullOrEmpty(ArticleID.Text) &&
        !string.IsNullOrEmpty(ArticleName.Text) &&
        !string.IsNullOrEmpty(ArticleDescription.Text) &&
        baseprice > 0;
  }

  private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
  {
     ArticleViewModel avm = (ArticleViewModel)DataContext;
     if (avm != null && avm.Save())
     {
        ArticleID.Text = String.Empty;
        ArticleName.Text = String.Empty;
        ArticleDescription.Text = String.Empty;
        ArticleBasePrice.Text = String.Empty;
     }
  }

Now, I put this user control on a window. When I hit Ctrl+S the command is executed. However, I also put a Save button on that window, next to this user control. When I click it I want to execute the same command (and I don't want to do another command binding in the window where the user control is hosted).

   <StackPanel>
      <local:ArticleControl x:Name="articleControl" />
      <Button Name="btnSave" 
              Content="Save" Width="100" 
              HorizontalAlignment="Left" 
              Command="{???}"/> <!-- what should I put here? -->
   </StackPanel>

But I do not know how to refer that saveCmd defined in the user control. I tried different things, some are completely wrong (they throw exception when running the app), some don't have any effect.

Command="{StaticResource saveCmd}"
Command="{StaticResource local:ArticleControl.saveCmd}"
Command="{x:Static local:Commands.Save}"

Any help is appreciated. Thank you.


The reason why the Save button will not cause the commandbindings of your other control to execute is because the Save button is outside the user control and therefore the command system will not look for a commandbinding in that control. The Command execution strategy is a bit like a bubbling event and will start from the focused item (the Button) and go up the visual tree until it finds the CommandBindings.

You can either implement the command binding in the parent control or set the CommandTarget property of the Save button to the user control.

Another approach is to set the FocusManager.IsFocusScope=True on the button or the container of the button. If you do this I suggest you read up on what IsFocusScope does but in a nutshell it will leave the input focus on whatever control has the focus when you press the button, instead of making the button the new input focus. This is generally used for toolbars or menu like structures.


Based on Patrick's suggestions, this is what I did:

  1. Put the command binding in the user control and implemented the handlers in the code-behind as shown in the original message.

  2. Used Command, CommandTarget and FocusManager properties on the button to point to the binding from the user control (ArticleUserControl is the x:Name of the user control).

This is how the XAML for the window looks:

<Window x:Class="MVVMModel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMModel"
        Title="MainWindow" Height="350" Width="525">
   <StackPanel>
      <local:ArticleControl x:Name="articleControl" />
      <Button Name="btnSave" Content="Save" Width="100" HorizontalAlignment="Left" 
              Command="local:Commands.Save"
              CommandTarget="{Binding ElementName=ArticleUserControl}"
              FocusManager.IsFocusScope="True" />
   </StackPanel>
</Window>


I think you just have to move your CommandBinding to a Resource Dictionary, so that it's available outside your UserControl!


Here is what I did to work, though I'm not particularly happy with the solution. If anyone knows a better approach, please do let me know.

I moved the logic for the commands handler in a separate, static class:

   static class CommandsCore
   {
      public static bool Save_CanExecute(ArticleControl ac)
      {
         double baseprice = 0;
         double.TryParse(ac.ArticleBasePrice.Text, out baseprice);

         return
            !string.IsNullOrEmpty(ac.ArticleID.Text) &&
            !string.IsNullOrEmpty(ac.ArticleName.Text) &&
            !string.IsNullOrEmpty(ac.ArticleDescription.Text) &&
            baseprice > 0;
      }

      public static void Save_Executed(ArticleControl ac)
      {
         ArticleViewModel avm = (ArticleViewModel)ac.DataContext;
         if (avm != null && avm.Save())
         {
            ac.ArticleID.Text = String.Empty;
            ac.ArticleName.Text = String.Empty;
            ac.ArticleDescription.Text = String.Empty;
            ac.ArticleBasePrice.Text = String.Empty;
         }
      }
   }

I kept the command binding in the user control as it was

   <UserControl.CommandBindings>
      <CommandBinding x:Name="saveCmd" 
                      Command="local:Commands.Save" 
                      CanExecute="CommandBinding_CanExecute"
                      Executed="CommandBinding_Executed"/>
   </UserControl.CommandBindings>

But in the handlers I called the two methods I just defined above.

  public void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
     e.CanExecute = CommandsCore.Save_CanExecute(this);
  }

  public void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
  {
     CommandsCore.Save_Executed(this);
  }

And then I did the same from the window where the control is used.

<Window x:Class="MVVMModel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMModel"
        Title="MainWindow" Height="350" Width="525">
   <Window.CommandBindings>
      <CommandBinding x:Name="saveCmd" 
                      Command="local:Commands.Save" 
                      CanExecute="CommandBinding_CanExecute"
                      Executed="CommandBinding_Executed"/>
   </Window.CommandBindings>

   <StackPanel>
      <local:ArticleControl x:Name="articleControl" />
      <Button Name="btnSave" Content="Save" Width="100" HorizontalAlignment="Left" 
              Command="local:Commands.Save"/>
   </StackPanel>
</Window>

and the handlers

  public void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
     e.CanExecute = CommandsCore.Save_CanExecute(articleControl);
  }

  public void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
  {
     CommandsCore.Save_Executed(articleControl);
  }

And this works, the Save button is enabled only when the fields are filled in appropriately and the command is executed correctly when clicking the button.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号