web-dev-qa-db-fra.com

WPF: Comment lier une commande au ListBoxItem en utilisant MVVM?

Je viens de commencer à apprendre MVVM. J'ai créé l'application à partir de rien en suivant ce tutoriel MVVM (je le recommande vivement à tous les débutants en MVVM). Fondamentalement, ce que j’ai créé jusqu’à présent est un ensemble de zones de texte dans lesquelles l’utilisateur ajoute ses données, un bouton permettant de sauvegarder ces données qui remplit ensuite le ListBox avec toutes les entrées effectuées. 

Voici où je me suis retrouvé coincé: je veux pouvoir double-cliquer sur un ListBoxItem et déclencher une commande que j'ai créée et ajoutée à mon ViewModel. Je ne sais pas comment terminer le côté XAML, c’est-à-dire que je ne sais pas comment lier cette commande au ListBox (Item). 

Voici XAML: 

...
<ListBox 
    Name="EntriesListBox" 
    Width="228" 
    Height="208" 
    Margin="138,12,0,0" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    ItemsSource="{Binding Entries}" />
...

Voici ViewModel: 

public class MainWindowViewModel : DependencyObject
{
    ...
    public IEntriesProvider Entries
    {
        get { return entries; }
    }

    private IEntriesProvider entries;
    public OpenEntryCommand OpenEntryCmd { get; set; }

    public MainWindowViewModel(IEntriesProvider source)
    {
        this.entries = source;
        ...
        this.OpenEntryCmd = new OpenEntryCommand(this);
    }
    ...
}

Et enfin, voici la OpenEntryCommand que je veux exécuter une fois que l'utilisateur double-clique sur l'élément dans EntriesListBox:

public class OpenEntryCommand : ICommand
{
    private MainWindowViewModel viewModel;

    public OpenEntryCommand(MainWindowViewModel viewModel)
    {
        this.viewModel = viewModel;
    }

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

    public bool CanExecute(object parameter)
    {
        return parameter is Entry;
    }

    public void Execute(object parameter)
    {
        string messageFormat = "Subject: {0}\nStart: {1}\nEnd: {2}";
        Entry entry = parameter as Entry;
        string message = string.Format(messageFormat, 
                                       entry.Subject, 
                                       entry.StartDate.ToShortDateString(), 
                                       entry.EndDate.ToShortDateString());

        MessageBox.Show(message, "Appointment");
    }
}

S'il vous plaît aider, je l'apprécierais. 

24
Boris

Malheureusement, seuls les contrôles dérivés ButtonBase ont la possibilité de lier des objets ICommand à leurs propriétés Command (pour l'événement Click).

Cependant, vous pouvez utiliser une API fournie par Blend pour mapper un événement (comme dans votre cas MouseDoubleClick sur la ListBox) à un objet ICommand

<ListBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding YourCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

Vous devrez définir: xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity" et avoir une référence à System.Windows.Interactivity.dll.

- EDIT -- Cela fait partie de WPF4, mais vous pouvez utiliser Microsoft.Windows.Interactivity si vous n’utilisez pas WPF4. Cette DLL provient de Blend SDK, qui ne nécessite pas Blend, à partir d'ici: http://www.Microsoft.com/downloads/fr/details.aspx?FamilyID=f1ae9a30-4928-411d-970b- e682ab179e17 & displaylang = fr

Mise à jour: J'ai trouvé quelque chose qui devrait vous aider. vérifiez ce lien dans MVVM Light Toolkit , qui contient une procédure pas à pas permettant de le faire, ainsi qu'un lien link vers les bibliothèques nécessaires. MVVM Light Toolkit est un framework très intéressant pour l’application de MVVM avec Silverlight, WPF et WP7.

J'espère que cela t'aides :)

61
AbdouMoumen

Ceci est compliqué à cause de l'événement DoubleClick. Il y a quelques façons de le faire:

  1. Gérez l'événement de double-clic dans le code derrière, puis appelez manuellement une commande/méthode sur votre ViewModel.
  2. Utilisez un comportement attaché pour router l'événement DoubleClick vers votre commande
  3. Utiliser un comportement de mélange pour mapper l'événement DoubleClick à votre commande

2 et 3 sont peut-être plus purs, mais franchement, 1 est plus facile, moins complexe et ne constitue pas la pire chose au monde. Pour un cas unique, j'utiliserais probablement l'approche n ° 1. 

Maintenant, si vous modifiiez vos exigences pour utiliser, par exemple, un hyperlien sur chaque élément, ce serait plus facile. Commencez par nommer l’élément racine dans votre XAML - par exemple, pour une fenêtre:

<Window .... Name="This">

Maintenant, dans le DataTemplate pour vos éléments ListBox, utilisez quelque chose comme ceci:

<ListBox ...>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Hyperlink 
        Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
        Text="{Binding Path=Name}" 
        />

La liaison ElementName vous permet de résoudre OpenEntryCmd à partir du contexte de votre ViewModel, plutôt que de l'élément de données spécifique. 

7
Paul Stovell

Le meilleur moyen de le faire est de créer un wrapper de contrôle utilisateur simple pour mon contenu, avec des propriétés de dépendance pour la commande et le paramètre.

La raison pour laquelle j'ai fait cela était dû au fait que le bouton ne transmettait pas l'événement click à mon ListBox, ce qui l'empêchait de sélectionner ListBoxItem.

CommandControl.xaml.cs:

public partial class CommandControl : UserControl
{
    public CommandControl()
    {
        MouseLeftButtonDown += OnMouseLeftButtonDown;
        InitializeComponent();
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        if (Command != null)
        {
            if (Command.CanExecute(CommandParameter))
            {
                Command.Execute(CommandParameter);
            }
        }
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand),
            typeof(CommandControl),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

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

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object),
            typeof(CommandControl),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

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

CommandControl.xaml:

<UserControl x:Class="WpfApp.UserControls.CommandControl"
         xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.Microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         Background="Transparent">
</UserControl>

Usage:

<ListBoxItem>
    <uc:CommandControl Command="{Binding LoadPageCommand}"
                       CommandParameter="{Binding HomePageViewModel}">
        <TextBlock Text="Home" Margin="0,0,0,5" VerticalAlignment="Center"
                   Foreground="White" FontSize="24" />
    </uc:CommandControl>
</ListBoxItem>

Le contenu peut être ce que vous voulez, et quand le contrôle est cliqué, il exécutera la commande.

EDIT: Ajout de Background="Transparent" à UserControl pour activer les événements de clic sur toute la zone du contrôle.

2
Shahin Dohan

C'est un peu un bidouillage, mais cela fonctionne bien et vous permet d'utiliser des commandes et d'éviter le code derrière. Cela présente également l’avantage supplémentaire de ne pas déclencher la commande lorsque vous double-cliquez (ou quel que soit votre déclencheur) dans la zone vide ScrollView en supposant que votre ListBoxItems ne remplit pas tout le conteneur.

Fondamentalement, il suffit de créer un DataTemplate pour votre ListBox composé de TextBlock et de lier la largeur de TextBlock à la largeur de ListBox, les marges et le remplissage à 0, et désactiver le défilement horizontal (parce que TextBlock saigneront au-delà des limites visibles de ScrollView déclenchant sinon la barre de défilement horizontale). Le seul bug que j'ai trouvé est que la commande ne se déclenche pas si l'utilisateur clique précisément sur la bordure du ListBoxItem, avec lequel je peux vivre.

Voici un exemple:

<ListBox
    x:Name="listBox"
    Width="400"
    Height="150"
    ScrollViewer.HorizontalScrollBarVisibility="Hidden"
    ItemsSource="{Binding ItemsSourceProperty}"
    SelectedItem="{Binding SelectedItemProperty}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Padding="0" 
                        Margin="0" 
                        Text="{Binding DisplayTextProperty}" 
                        Width="{Binding ElementName=listBox, Path=Width}">
                <TextBlock.InputBindings>
                    <MouseBinding 
                        Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectProjectCommand}" 
                                    Gesture="LeftDoubleClick" />
                </TextBlock.InputBindings>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
0
manic_coder