WPF MVVM: les commandes sont faciles. Comment connecter View et ViewModel avec RoutedEvent

Suppose que j'ai une vue implémentée comme un empattement de données dans un dictionnaire de ressources. Et j'ai un modèle de vue correspondant. La liaison Commandes sont faciles. Mais que se passe-t-il si ma vue contient un contrôle tel qu'une ListBox, et que je dois publier un événement à l'échelle de L'application (en utilisant L'Event Aggreagtor de Prism) basé sur l'élément modifié sur la liste.

si ListBox supporte une commande, je pourrais simplement la lier à une commande dans le ViewModel et publier l'événement. Mais Listbox n'est pas permettre à une telle option. Comment puis-je combler ce?

EDIT: Beaucoup de grandes réponses.

regardez ce lien http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx

Merci

Ariel

15
demandé sur ArielBH 2009-05-10 18:35:52

7 réponses

une option est d'étendre le contrôle en question et d'ajouter un support pour la commande particulière dont vous avez besoin. Par exemple, J'ai modifié ListView avant pour supporter l'événement ItemActivated et la commande associée.

8
répondu Kent Boogaart 2011-09-27 15:14:08

au Lieu d'essayer de lier une commande lorsque l'élément de changements, j'ai regardé le problème d'une autre manière.

Si vous liez l'élément sélectionné de la liste pour une propriété dans le ViewModel, puis lorsque cette propriété est modifiée, vous pouvez publier l'événement. De cette façon, le ViewModel reste la source de l'événement et il est déclenché par le changement d'élément, ce qui est ce que vous voulez.

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />

...

public class ViewModel
{
    public IEnumerable<Item> Items { get; set; } 

    private Item selectedItem;
    public Item SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (selectedItem == value)
                return;
            selectedItem = value;
            // Publish event when the selected item changes
        }
}
42
répondu Cameron MacFarland 2009-05-17 12:49:43

étendre le contrôle pour soutenir ICommandSource et décider quelle action devrait déclencher la commande.

j'ai fait cela avec Combo Box et j'ai utilisé OnSelectionChanged comme déclencheur pour la commande. Tout d'abord, je vais montrer dans XAML comment je lie la commande au ComboBox de contrôle étendu que j'ai appelé CommandComboBox, puis je vais montrer le code pour CommandComboBox qui ajoute le support pour L'ICommandSource à ComboBox.

1) Utilisant CommandComboBox dans votre code XAML:

dans vos déclarations d'espace de noms XAML include

   xmlns:custom="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary">

utilisez la commande CommandComboBox à la place de ComboBox et liez la commande comme suit: notez que dans cet exemple j'ai défini une commande appelée SetLanguageCommand im my ViewModel et je passe la valeur sélectionnée pour cette ComboBox comme paramètre à la commande.

 <custom:CommandComboBox 
    x:Name="ux_cbSelectLanguage"
    ItemsSource="{Binding Path = ImagesAndCultures}"
    ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"           
    Command="{Binding Path=SetLanguageCommand, Mode=Default}"
    CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
    IsSynchronizedWithCurrentItem="True" 
    HorizontalAlignment="Right" 
    VerticalAlignment="Center" 
    Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
 />

2) le code pour Commandcombox

Le code du fichier CommandComboBox.cs est inclus ci-dessous. J'ai ajouté ce fichier à une bibliothèque de classe appelée WpfCommandControlsLibrary et j'en ai fait un projet séparé pour pouvoir facilement ajouter des commandes extend à n'importe quelle solution nécessaire pour les utiliser et donc je pourrais facilement ajouter des commandes WPF supplémentaires et les étendre pour supporter L'inteface ICommandSource.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfCommandControlsLibrary
{
   /// <summary>
   /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
   ///
   /// Step 1a) Using this custom control in a XAML file that exists in the current project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
   ///
   ///
   /// Step 1b) Using this custom control in a XAML file that exists in a different project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary"
   ///
   /// You will also need to add a project reference from the project where the XAML file lives
   /// to this project and Rebuild to avoid compilation errors:
   ///
   ///     Right click on the target project in the Solution Explorer and
   ///     "Add Reference"->"Projects"->[Select this project]
   ///
   ///
   /// Step 2)
   /// Go ahead and use your control in the XAML file.
   ///
   ///     <MyNamespace:CustomControl1/>
   ///
   /// </summary>

   public class CommandComboBox : ComboBox, ICommandSource
   {
      public CommandComboBox() : base()
      {
      }

  #region Dependency Properties
  // Make Command a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
          "Command",
          typeof(ICommand),
          typeof(CommandComboBox),
          new PropertyMetadata((ICommand)null,
          new PropertyChangedCallback(CommandChanged)));

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

  // Make CommandTarget a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandTargetProperty =
      DependencyProperty.Register(
          "CommandTarget",
          typeof(IInputElement),
          typeof(CommandComboBox),
          new PropertyMetadata((IInputElement)null));

  public IInputElement CommandTarget
  {
     get
     {
        return (IInputElement)GetValue(CommandTargetProperty);
     }
     set
     {
        SetValue(CommandTargetProperty, value);
     }
  }

  // Make CommandParameter a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
          "CommandParameter",
          typeof(object),
          typeof(CommandComboBox),
          new PropertyMetadata((object)null));

  public object CommandParameter
  {
     get
     {
        return (object)GetValue(CommandParameterProperty);
     }
     set
     {
        SetValue(CommandParameterProperty, value);
     }
  }

  #endregion

  // Command dependency property change callback.
  private static void CommandChanged(DependencyObject d,
      DependencyPropertyChangedEventArgs e)
  {
     CommandComboBox cb = (CommandComboBox)d;
     cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
  }

  // Add a new command to the Command Property.
  private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
  {
     // If oldCommand is not null, then we need to remove the handlers.
     if (oldCommand != null)
     {
        RemoveCommand(oldCommand, newCommand);
     }
     AddCommand(oldCommand, newCommand);
  }

  // Remove an old command from the Command Property.
  private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = CanExecuteChanged;
     oldCommand.CanExecuteChanged -= handler;
  }

  // Add the command.
  private void AddCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = new EventHandler(CanExecuteChanged);
     canExecuteChangedHandler = handler;
     if (newCommand != null)
     {
        newCommand.CanExecuteChanged += canExecuteChangedHandler;
     }
  }
  private void CanExecuteChanged(object sender, EventArgs e)
  {

     if (this.Command != null)
     {
        RoutedCommand command = this.Command as RoutedCommand;

        // If a RoutedCommand.
        if (command != null)
        {
           if (command.CanExecute(CommandParameter, CommandTarget))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
        // If a not RoutedCommand.
        else
        {
           if (Command.CanExecute(CommandParameter))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
     }
  }

  // If Command is defined, selecting a combo box item will invoke the command;
  // Otherwise, combo box will behave normally.
  protected override void OnSelectionChanged(SelectionChangedEventArgs e)
  {
     base.OnSelectionChanged(e);

     if (this.Command != null)
     {
        RoutedCommand command = Command as RoutedCommand;

        if (command != null)
        {
           command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
           ((ICommand)Command).Execute(CommandParameter);
        }
     }
  }

  // Keep a copy of the handler so it doesn't get garbage collected.
  private static EventHandler canExecuteChangedHandler;

  }
}
16
répondu eesh 2009-05-12 15:01:28

une excellente solution à ce type de problème vient de l'utilisation des propriétés attachées. Marlon Grech a pris l'utilisation des propriétés attachées au niveau suivant en créant les comportements de commande attachés . En utilisant ceux-ci, il est possible de lier n'importe quelle commande existant dans un ViewModel à n'importe quel événement existant dans la vue.

C'est quelque chose que j'utilise beaucoup pour traiter des problèmes similaires avec les boîtes de listes, où je veux qu'elles s'ouvrent, ou éditent ou font quelque chose action sur un double-clic.

dans cet exemple, j'utilise une version plus ancienne des comportements de commande attachés, mais l'effet est le même. J'ai un style qui est utilisé pour les ListBoxItems que je touche explicitement. Cependant, il serait assez facile de créer une application ou une fenêtre de style large s'appliquant à tous les ListBoxItems qui définit les commandes à un niveau beaucoup plus élevé. Puis, à chaque fois que l'événement pour le ListBoxItem attaché à la Commandebehavior.La propriété de l'événement serait le feu, il au lieu de cela, déclenche la commande attachée.

<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox  ItemsSource="{Binding MyItems}"
          ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>
2
répondu rmoore 2009-05-12 00:00:26

personne n'a répondu. Donc j'ai abandonné et j'ai déplacé l'implémentation de la vue en dehors du dictionnaire dans un UserControl régulier, je lui ai injecté une référence au ViewModel.

maintenant quand la liste de diffusion démarre L'événement il appelle le ViewModel et de là tout est à nouveau possible.

Ariel

1
répondu ArielBH 2009-05-11 07:32:05

j'ai écrit des comportements (propriétés attachées) pour le faire, et il y a encore des cas où j'en ai besoin.

dans le cas habituel cependant, en liant simplement un événement à une commande, vous pouvez tout faire dans Xaml si vous avez installé Blend SDK 4. Notez que vous devrez ajouter une référence au système.Windows.Interactivité.dll, et pour redistribuer cette assemblée.

cet exemple invoque un ICommand DragEnterCommand sur le Modèlevue lorsque l'événement DragEnter de la grille est allumé:

<UserControl xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
    <Grid>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="DragEnter">
                <i:InvokeCommandAction Command="{Binding DragEnterCommand}" CommandParameter="{Binding ...}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
</UserControl>
1
répondu Mike Fuchs 2013-03-25 15:16:38

Essayez d'utiliser Prism 2 .

il est livré avec de grandes extensions à commander et ouvre de nombreuses nouvelles posibilités (comme des commandes à être lié à l'arbre visuel).

0
répondu Jarek Kardas 2009-11-03 07:26:07