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, который я хочу выполнить после того, как пользователь дважды щелкнет элемент в EntryListBox:
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");
}
}
Пожалуйста, помогите, я был бы признателен.
Ответы
Ответ 1
К сожалению, только производные элементы управления 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
.
- EDIT -
Это часть 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.
Надеюсь, что это поможет:)
Ответ 2
Это сложно сделать из-за события 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, а не конкретного элемента данных.
Ответ 3
Я считаю, что лучший способ сделать это - создать простую оболочку пользовательского элемента управления для моего содержимого с зависимыми свойствами для команды и параметра.
Причина, по которой я это делал, была связана с тем, что 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>
Содержимое может быть любым, и когда элемент управления будет нажат, он выполнит команду.
EDIT: Добавлен Background="Transparent"
в UserControl для включения событий щелчка по всей области элемента управления.
Ответ 4
Это что-то вроде хака, но он работает хорошо и позволяет вам использовать команды и избегать кода позади. Это также дает дополнительное преимущество, ScrollView
что вы не запускаете команду, когда вы дважды щелкаете (или каким бы ни был ваш триггер) в пустой области ScrollView
предполагая, что ваши ListBoxItems
не заполняют весь контейнер.
По сути, просто создайте DataTemplate
для вашего ListBox
который состоит из TextBlock
и привяжите ширину TextBlock
к ширине ListBox
, установите поля и отступы в 0 и отключите горизонтальную прокрутку (потому что TextBlock
будет кровоточить за пределами видимого границы ScrollView
вызывающего горизонтальную полосу прокрутки в противном случае). Единственная найденная ошибка - команда не сработает, если пользователь ListBoxItem
точно по границе 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>