WPF: Как привязать команду к ListBoxItem с помощью MVVM?
Я только начал изучать MVVM. Я сделал приложение с нуля, следуя этому руководству по MVVM (я настоятельно рекомендую его всем начинающим MVVM). По сути, я создал пару текстовых полей, в которые пользователь добавляет свои данные, кнопку для сохранения этих данных, которая впоследствии заполняет ListBox всеми сделанными записями.
Вот где я застрял: я хочу иметь возможность дважды щелкнуть элемент ListBoxItem и вызвать команду, которую я создал и добавил в свою ViewModel. Я не знаю, как закончить сторону 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");
}
}
Пожалуйста, помогите, я был бы признателен.
6 ответов
К сожалению, только 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.
Надеюсь это поможет:)
Это сложно сделать из-за события DoubleClick. Есть несколько способов сделать это:
- Обработайте событие двойного щелчка в коде позади, а затем вручную вызовите команду / метод в вашей ViewModel
- Используйте прикрепленное поведение, чтобы направить событие DoubleClick к вашей Команде
- Используйте Blend Behavior для сопоставления события DoubleClick с вашей командой
2 и 3 могут быть более чистыми, но, честно говоря, 1 проще, менее сложен и не является худшей вещью в мире. Для одноразового случая я бы, вероятно, использовал подход № 1.
Теперь, если вы изменили свои требования для использования, скажем, гиперссылки на каждый элемент, это было бы проще. Начните с именования корневого элемента в вашем XAML - например, для окна:
<Window .... Name="This">
Теперь в DataTemplate для ваших элементов ListBox используйте что-то вроде этого:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Hyperlink
Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
Text="{Binding Path=Name}"
/>
Привязка ElementName позволяет вам разрешить OpenEntryCmd из контекста вашей ViewModel, а не из конкретного элемента данных.
Я считаю, что лучший способ сделать это - создать простую пользовательскую оболочку управления для моего контента со свойствами зависимости для команды и параметра.
Причина, по которой я это сделал, была в том, что Button не отправляла событие 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, чтобы включить события нажатия на всю область элемента управления.
Это что-то вроде хака, но он хорошо работает и позволяет вам использовать команды и избегать кода позади. Это также имеет дополнительное преимущество, заключающееся в том, что команда не запускается, когда вы дважды щелкаете мышью (или каким бы ни был ваш триггер) в пустом месте. 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>
Недавно мне нужно было вызвать
ICommand
при двойном щелчке также.
Лично мне не нравится
DataTemplate
метод, поскольку он привязан к содержимому внутри контейнера, а не к самому контейнеру. Я решил использовать прикрепленное свойство, чтобы назначить
InputBinding
на контейнере. Требуется немного больше смазки для локтей, но она работает хорошо.
Во-первых, нам нужно создать присоединенный класс свойств. Я создал свой чуть более общий подход к любому классу, производному от
FrameworkElement
на всякий случай снова столкнусь с этим с другим визуалом.
public class FrameworkElementAttachedProperties : DependencyObject
{
public static readonly DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(InputBinding),
typeof(FrameworkElementAttachedProperties), new PropertyMetadata(null, OnDoubleClickChanged));
public static void SetDoubleClick(FrameworkElement element, InputBinding value)
{
element.SetValue(DoubleClickProperty, value);
}
public static InputBinding GetDoubleClick(FrameworkElement element)
{
return (InputBinding)element.GetValue(DoubleClickProperty);
}
private static void OnDoubleClickChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = obj as FrameworkElement;
/// Potentially throw an exception if an object is not a FrameworkElement (is null).
if(e.NewValue != null)
{
element.InputBindings.Add(e.NewValue as InputBinding);
}
if(e.OldValue != null)
{
element.InputBindings.Remove(e.OldValue as InputBinding);
}
}
}
Затем последний шаг - переопределить базовый стиль контейнера для.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource ListBoxItem}">
<Setter Property="local:FrameworkElementAttachedProperties.DoubleClick">
<Setter.Value>
<MouseBinding Command="{Binding OnListBoxItemDoubleClickCommand}"
MouseAction="LeftDoubleClick"/>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
Теперь, в любое время
ListBoxItem
двойного щелчка, он запустит наш
OnListBoxItemDoubleClickCommand
.
Если вы ищете красивое простое решение, которое использует взаимодействия вместо того, чтобы возиться с пользовательскими элементами управления, кодом, привязками ввода, настраиваемыми присоединенными свойствами и т. Д.
И вам нужно что-то, что работает на уровне ListBoxItem, то есть не на уровне ListBox в соответствии с (неправильно) принятым решением.
Тогда вот фрагмент простой кнопки, такой как действие щелчка.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent">
<!-- insert your visuals here -->
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseUp">
<b:InvokeCommandAction Command="{Binding YourCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Обратите внимание, что background="Transparent" требуется для обеспечения интерактивности всей сетки, а не только ее содержимого.