WPF: Как привязать команду к ListBoxItem с помощью MVVM? - PullRequest
26 голосов
/ 05 апреля 2011

Я только начал изучать MVVM.Я сделал приложение с нуля, следуя этому MVVM-уроку (я настоятельно рекомендую его всем начинающим MVVM).По сути, я создал пару текстовых полей, в которые пользователь добавляет свои данные, кнопку для сохранения этих данных, которая впоследствии заполняет ListBox всеми сделанными записями.

Вот где я застрял: я хочу иметь возможность дважды щелкнуть элемент ListBoxItem и вызвать команду, которую я создал и добавил в мою модель представления.Я не знаю, как закончить сторону XAML, т.е. я не знаю, как привязать эту команду к ListBox (Item).

Вот XAML:

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

Вот 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);
    }
    ...
}

И, наконец, вот команда OpenEntryCommand, которую я хочу выполнить, когда пользователь дважды щелкнет элементв 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");
    }
}

Пожалуйста, помогите, я был бы признателен.

Ответы [ 4 ]

63 голосов
/ 05 апреля 2011

К сожалению, только ButtonBase производные элементы управления имеют возможность привязки ICommand объектов к их Command свойствам (для события Click).

Однако вы можете использовать API, предоставленный Blend, для сопоставления события (как в вашем случае MouseDoubleClick на ListBox) с ICommand объектом.

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

Вы должны будете определить: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" и иметь ссылку на System.Windows.Interactivity.dll.

- РЕДАКТИРОВАТЬ - Это часть WPF4, но вы можете использовать Microsoft.Windows.Interactivity, если вы не используете WPF4. Это dll из Blend SDK, который не требует Blend, отсюда: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang=en

Обновление : Я нашел то, что должно вам помочь. проверьте эту ссылку на MVVM Light Toolkit , в которой содержится пошаговое руководство о том, как это сделать, а также ссылку на необходимые библиотеки. MVVM Light Toolkit - очень интересная среда для применения MVVM с Silverlight, WPF и WP7.

Надеюсь, это поможет:)

7 голосов
/ 05 апреля 2011

Это сложно сделать из-за события DoubleClick.Есть несколько способов сделать это:

  1. Обрабатывать событие двойного щелчка в коде и затем вручную вызывать команду / метод на вашей ViewModel
  2. Использовать присоединенное поведение дляперенаправьте событие DoubleClick на вашу Команду
  3. Используйте Blend Behavior для , сопоставьте событие DoubleClick с вашей командой

2 и 3 могутбыть более чистым, но, честно говоря, 1 проще, менее сложным и не худшим в мире.Для одноразового случая я бы, вероятно, использовал подход № 1.

Теперь, если вы измените свои требования для использования, скажем, гиперссылки на каждый элемент, это будет проще.Начните с именования корневого элемента в вашем XAML - например, для Window:

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

Теперь, в DataTemplate для ваших элементов ListBox, используйте что-то вроде этого:

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

Привязка ElementName позволяет вам разрешить OpenEntryCmd из контекста вашей ViewModel, а не из конкретного элемента данных.

2 голосов
/ 30 января 2019

Это что-то вроде хака, но он работает хорошо и позволяет вам использовать команды и избегать кода позади. Это также дает дополнительное преимущество, заключающееся в том, что вы не запускаете команду, когда вы дважды щелкаете (или каким бы ни был ваш триггер) в пустой области ScrollView, предполагая, что ListBoxItems не заполняет весь контейнер.

По сути, просто создайте DataTemplate для вашего ListBox, который состоит из TextBlock и привязайте ширину TextBlock к ширине ListBox, установите поля и отступы равными 0, и отключите горизонтальную прокрутку (потому что TextBlock будет выходить за пределы видимых границ ScrollView, вызывая горизонтальную полосу прокрутки в противном случае). Единственная ошибка, которую я обнаружил, заключается в том, что команда не сработает, если пользователь щелкнет точно по границе ListBoxItem, с которой я могу жить.

Вот пример:

<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>
2 голосов
/ 07 марта 2017

Я считаю, что лучший способ сделать это - создать простую пользовательскую оболочку для моего контента со свойствами зависимостей для команды и параметра.

Причина, по которой я это сделал, заключалась в том, что кнопка не всплываетсобытие click для моего ListBox, которое не позволило ему выбрать 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>

Использование:

<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>

Содержимое может быть любым, и при нажатии на элемент управления он выполнит команду.

РЕДАКТИРОВАТЬ: Добавлено Background="Transparent" в UserControl для включениясобытия клика по всей области элемента управления.

...