我正在对一个简单的应用程序进行一些重构以跟踪MVVM,我的问题是如何将SelectionChanged事件从我的代码中移出到viewModel?我已经看了一些绑定元素到命令的例子,但是并没有完全掌握它。谁能帮忙解决这个问题。谢谢!
任何人都可以使用以下代码提供解决方案吗?非常感谢!
public partial class MyAppView : Window
{
public MyAppView()
{
InitializeComponent();
this.DataContext = new MyAppViewModel ();
// Insert code required on object creation below this point.
}
private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
//TODO: Add event handler implementation here.
//for each selected contact get the labels and put in collection
ObservableCollection<AggregatedLabelModel> contactListLabels = new ObservableCollection<AggregatedLabelModel>();
foreach (ContactListModel contactList in contactsList.SelectedItems)
{
foreach (AggregatedLabelModel aggLabel in contactList.AggLabels)
{
contactListLabels.Add(aggLabel);
}
}
//aggregate the contactListLabels by name
ListCollectionView selectedLabelsView = new ListCollectionView(contactListLabels);
selectedLabelsView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
tagsList.ItemsSource = selectedLabelsView.Groups;
}
}
答案 0 :(得分:95)
您应该将EventTrigger
与来自Windows.Interactivity命名空间的InvokeCommandAction
结合使用。这是一个例子:
<ListBox ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
您可以通过System.Windows.Interactivity
引用Add reference > Assemblies > Extensions
。
完整的i
命名空间为:xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
。
答案 1 :(得分:10)
这个问题有类似的问题。
WPF MVVM : Commands are easy. How to Connect View and ViewModel with RoutedEvent
我处理这个问题的方法是在ViewModel中有一个SelectedItem属性,然后将ListBox的SelectedItem绑定到该属性。
答案 2 :(得分:8)
要重构这一点,你需要改变思路。您将不再处理“选择已更改”事件,而是将所选项目存储在viewmodel中。然后,您将使用双向数据绑定,以便在用户选择项目时,您的viewmodel会更新,当您更改所选项目时,您的视图会更新。
答案 3 :(得分:7)
您最好的选择是使用Windows.Interactivity
。使用EventTriggers
将ICommand
附加到任何RoutedEvent
。
以下是一篇可以帮助您入门的文章:Silverlight and WPF Behaviours and Triggers
答案 4 :(得分:2)
<ListBox SelectionChanged="{eb:EventBinding Command=SelectedItemChangedCommand, CommandParameter=$e}">
</ListBox>
<强>命令强>
{eb:EventBinding}(查找命令的简单命名模式)
{eb:EventBinding Command = CommandName}
<强> CommandParameter 强>
$ e(EventAgrs)
$ this或$ this.Property
的字符串
答案 5 :(得分:1)
我会按照question
中的最佳答案进行操作基本上,您的视图模型将包含所有项目的列表和所选项目的列表。然后,您可以将行为附加到管理所选项目列表的列表框中。
这样做意味着您在后面的代码中没有任何内容,并且xaml相当容易理解,也可以在您应用的其他地方重复使用该行为。
<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />
答案 6 :(得分:1)
有时,当需要绑定自定义用户控件的事件时,通过Interactivity触发器将命令绑定到命令的解决方案不起作用。 在这种情况下,您可以使用自定义行为。
声明绑定行为,如:
public class PageChangedBehavior
{
#region Attached property
public static ICommand PageChangedCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(PageChangedCommandProperty);
}
public static void SetPageChangedCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(PageChangedCommandProperty, value);
}
public static readonly DependencyProperty PageChangedCommandProperty =
DependencyProperty.RegisterAttached("PageChangedCommand", typeof(ICommand), typeof(PageChangedBehavior),
new PropertyMetadata(null, OnPageChanged));
#endregion
#region Attached property handler
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as PageControl;
if (control != null)
{
if (e.NewValue != null)
{
control.PageChanged += PageControl_PageChanged;
}
else
{
control.PageChanged -= PageControl_PageChanged;
}
}
}
static void PageControl_PageChanged(object sender, int page)
{
ICommand command = PageChangedCommand(sender as DependencyObject);
if (command != null)
{
command.Execute(page);
}
}
#endregion
}
然后将其绑定到xaml中的命令:
<controls:PageControl
Grid.Row="2"
CurrentPage="{Binding Path=UsersSearchModel.Page,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PerPage="{Binding Path=UsersSearchModel.PageSize,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Count="{Binding Path=UsersSearchModel.SearchResults.TotalItemCount}"
behaviors:PageChangedBehavior.PageChangedCommand="{Binding PageChangedCommand}">
</controls:PageControl>
答案 7 :(得分:1)
考虑Microsoft.Xaml.Behaviors.Wpf,它的所有者是Microsoft
,您可以在该页面上看到。
System.Windows.Interactivity.WPF的所有者是mthamil
,有人可以告诉我它可靠吗?
Microsoft.Xaml.Behaviors.Wpf
的示例:
<UserControl ...
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
...>
<Button x:Name="button">
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
<behaviors:InvokeCommandAction Command="{Binding ClickCommand}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</Button>
</UserControl>
答案 8 :(得分:0)
正如@Cameron MacFarland所提到的,我只是双向绑定到viewModel上的属性。在属性设置器中,您可以执行所需的任何逻辑,例如根据您的要求添加到联系人列表。
但是,我不一定会调用属性'SelectedItem',因为viewModel不应该知道视图层以及它如何与它的属性进行交互。我称之为CurrentContact之类的东西。
显然这是除非你只想创建命令作为练习等练习。
答案 9 :(得分:0)
这是使用MarkupExtension
的实现。尽管具有低级性质(在这种情况下是必需的),但是XAML代码非常简单:
XAML
<SomeControl Click="{local:EventBinding EventToCommand}" CommandParameter="{local:Int32 12345}" />
Marup扩展
public class EventBindingExtension : MarkupExtension
{
private static readonly MethodInfo EventHandlerImplMethod = typeof(EventBindingExtension).GetMethod(nameof(EventHandlerImpl), new[] { typeof(object), typeof(string) });
public string Command { get; set; }
public EventBindingExtension()
{
}
public EventBindingExtension(string command) : this()
{
Command = command;
}
// Do not use!!
public static void EventHandlerImpl(object sender, string commandName)
{
if (sender is FrameworkElement frameworkElement)
{
object dataContext = frameworkElement.DataContext;
if (dataContext?.GetType().GetProperty(commandName)?.GetValue(dataContext) is ICommand command)
{
object commandParameter = (frameworkElement as ICommandSource)?.CommandParameter;
if (command.CanExecute(commandParameter)) command.Execute(commandParameter);
}
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider &&
targetProvider.TargetObject is FrameworkElement targetObject &&
targetProvider.TargetProperty is MemberInfo memberInfo)
{
Type eventHandlerType;
if (memberInfo is EventInfo eventInfo) eventHandlerType = eventInfo.EventHandlerType;
else if (memberInfo is MethodInfo methodInfo) eventHandlerType = methodInfo.GetParameters()[1].ParameterType;
else return null;
MethodInfo handler = eventHandlerType.GetMethod("Invoke");
DynamicMethod method = new DynamicMethod("", handler.ReturnType, new[] { typeof(object), typeof(object) });
ILGenerator ilGenerator = method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg, 0);
ilGenerator.Emit(OpCodes.Ldstr, Command);
ilGenerator.Emit(OpCodes.Call, EventHandlerImplMethod);
ilGenerator.Emit(OpCodes.Ret);
return method.CreateDelegate(eventHandlerType);
}
else
{
throw new InvalidOperationException("Could not create event binding.");
}
}
}
答案 10 :(得分:0)
我知道现在有点晚了,但是,微软已经将其Xaml.Behaviors开源了,现在只需要一个名称空间就可以轻松使用交互性。
然后像这样使用它,
<Button Width="150" Style="{DynamicResource MaterialDesignRaisedDarkButton}">
<behaviours:Interaction.Triggers>
<behaviours:EventTrigger EventName="Click">
<behaviours:InvokeCommandAction Command="{Binding OpenCommand}" PassEventArgsToCommand="True"/>
</behaviours:EventTrigger>
</behaviours:Interaction.Triggers>
Open
</Button>
PassEventArgsToCommand =“ True”应该设置为True,并且实现的RelayCommand可以将RoutedEventArgs或对象作为模板。如果将对象用作参数类型,则将其强制转换为适当的事件类型。
该命令将如下所示
OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });
命令方法如下所示
private void OnOpenClicked(object parameter)
{
Logger.Info(parameter?.GetType().Name);
}
“参数”将为路由事件对象。
还有日志,以防您好奇
2020-12-15 11:40:36.3600 | INFO | MyApplication.ViewModels.MainWindowViewModel | RoutedEventArgs
您可以看到记录的TypeName是RoutedEventArgs
RelayCommand的实现可以在这里找到。
PS:您可以绑定到任何控件的任何事件。类似于Window的Closing事件,您将获得相应的事件。