Answers:
请,后面的代码根本不是一件坏事。不幸的是,WPF社区中的很多人都错了。
MVVM不是消除后面代码的模式。它将视图部分(外观,动画等)与逻辑部分(工作流)分开。此外,您可以对逻辑部分进行单元测试。
我知道您必须在后面编写代码的场景足够多,因为数据绑定并不能解决所有问题。在您的方案中,我将在文件后面的代码中处理DoubleClick事件,并将此调用委托给ViewModel。
可以在以下位置找到示例应用程序,这些示例应用程序使用背后的代码并仍实现MVVM分离:
WPF应用程序框架(WAF) - http://waf.codeplex.com
我能够使它与.NET 4.5一起使用。看起来很简单,不需要任何第三方或代码。
<ListView ItemsSource="{Binding Data}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="2">
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowDetailCommand}"/>
</Grid.InputBindings>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="..\images\48.png" Width="48" Height="48"/>
<TextBlock Grid.Row="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
InputBindings
.NET 3.0中可用的内容,而Silverlight 中不可用。
我喜欢使用“ 附加命令行为和命令”。Marlon Grech很好地实现了“附加命令行为”。使用这些,我们可以将样式分配给ListView的ItemContainerStyle属性将为每个ListViewItem设置命令。
在这里,我们设置要在MouseDoubleClick事件上触发的命令,而CommandParameter将是我们单击的数据对象。在这里,我将沿着可视化树前进以获取我正在使用的命令,但是您可以轻松地创建应用程序范围的命令。
<Style x:Key="Local_OpenEntityStyle"
TargetType="{x:Type ListViewItem}">
<Setter Property="acb:CommandBehavior.Event"
Value="MouseDoubleClick" />
<Setter Property="acb:CommandBehavior.Command"
Value="{Binding ElementName=uiEntityListDisplay, Path=DataContext.OpenEntityCommand}" />
<Setter Property="acb:CommandBehavior.CommandParameter"
Value="{Binding}" />
</Style>
对于命令,您可以直接实现ICommand,也可以使用MVVM Toolkit中的一些帮助程序。。
acb:
= AttachedCommandBehavior。该代码可以在回答第一个链接中找到
我发现使用Blend SDK Event触发器可以非常简便地完成此任务。干净的MVVM,可重复使用,没有任何代码隐藏。
您可能已经有以下内容:
<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">
现在,如果尚未使用ListViewItem,则为其添加ControlTemplate:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}" />
</ControlTemplate>
</Setter.Value>
</Setter>
GridViewRowPresenter将是构成列表行元素的所有“内部”元素的可视根。现在,我们可以在其中插入触发器以查找MouseDoubleClick路由的事件,然后通过InvokeCommandAction调用命令,如下所示:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</GridViewRowPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
如果您的可视元素位于GridRowPresenter上方(以网格开头可能是probalby),则也可以将Trigger放在此处。
不幸的是,并非从每个可视元素生成MouseDoubleClick事件(例如,它们是从控件生成的,而不是从FrameworkElements生成的)。一种解决方法是从EventTrigger派生一个类,并查找ClickCount为2的MouseButtonEventArgs。这将有效过滤掉所有非MouseButtonEvents和所有带有ClickCount!= 2的MoseButtonEvents。
class DoubleClickEventTrigger : EventTrigger
{
protected override void OnEvent(EventArgs eventArgs)
{
var e = eventArgs as MouseButtonEventArgs;
if (e == null)
{
return;
}
if (e.ClickCount == 2)
{
base.OnEvent(eventArgs);
}
}
}
现在我们可以编写以下代码(“ h”是上面的帮助程序类的命名空间):
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}">
<i:Interaction.Triggers>
<h:DoubleClickEventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
</h:DoubleClickEventTrigger>
</i:Interaction.Triggers>
</GridViewRowPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
我意识到这个讨论已经进行了一年,但是对于.NET 4,对这个解决方案有什么想法吗?我完全同意MVVM的目的不是消除文件背后的代码。我也非常强烈地认为,仅仅是因为某些事情很复杂,并不意味着它会更好。这是我在后面的代码中输入的内容:
private void ButtonClick(object sender, RoutedEventArgs e)
{
dynamic viewModel = DataContext;
viewModel.ButtonClick(sender, e);
}
我发现创建视图时链接命令更简单:
var r = new MyView();
r.MouseDoubleClick += (s, ev) => ViewModel.MyCommand.Execute(null);
BindAndShow(r, ViewModel);
在我的情况下BindAndShow
看起来像这样(updatecontrols + avalondock):
private void BindAndShow(DockableContent view, object viewModel)
{
view.DataContext = ForView.Wrap(viewModel);
view.ShowAsDocument(dockManager);
view.Focus();
}
尽管该方法可以与您打开新视图的任何方法一起使用。
我看到了rushui的解决方案具有InuptBindings但是我仍然无法访问没有文本的ListViewItem区域-即使在将背景设置为透明后,因此我也使用了不同的模板来解决它。
此模板用于ListViewItem已选择并处于活动状态时:
<ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}">
<Border Background="LightBlue" HorizontalAlignment="Stretch">
<!-- Bind the double click to a command in the parent view model -->
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}"
CommandParameter="{Binding}" />
</Border.InputBindings>
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
此模板用于选择ListViewItem且该模板处于非活动状态时:
<ControlTemplate x:Key="SelectedInactiveTemplate" TargetType="{x:Type ListViewItem}">
<Border Background="Lavender" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
这是用于ListViewItem的默认样式:
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="Selector.IsSelectionActive" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedActiveTemplate}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="Selector.IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedInactiveTemplate}" />
</MultiTrigger>
</Style.Triggers>
</Style>
我不喜欢重复TextBlock及其文本绑定,我不知道II只能在一个位置声明它。
我希望这可以帮助别人!
listviewitem
,则他们可能不在乎是否已选择它。同样重要的是要注意,可能还需要调整高光效果以匹配listview
样式。投票赞成。
我通过使用交互库成功使用.Net 4.7框架实现了此功能,首先请确保在XAML文件中声明了名称空间
xmlns:i =“ http://schemas.microsoft.com/expression/2010/interactivity”
然后在ListView内使用其各自的InvokeCommandAction设置事件触发器,如下所示。
视图:
<ListView x:Name="lv" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=AppsSource}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=lv, Path=SelectedItem}"
Command="{Binding OnOpenLinkCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Developed By" DisplayMemberBinding="{Binding DevelopedBy}" />
</GridView>
</ListView.View>
</ListView>
修改上面的代码应该足以使双击事件在您的ViewModel上起作用,但是我在示例中添加了Model和View Model类,以便您有完整的想法。
模型:
public class ApplicationModel
{
public string Name { get; set; }
public string DevelopedBy { get; set; }
}
查看模型:
public class AppListVM : BaseVM
{
public AppListVM()
{
_onOpenLinkCommand = new DelegateCommand(OnOpenLink);
_appsSource = new ObservableCollection<ApplicationModel>();
_appsSource.Add(new ApplicationModel("TEST", "Luis"));
_appsSource.Add(new ApplicationModel("PROD", "Laurent"));
}
private ObservableCollection<ApplicationModel> _appsSource = null;
public ObservableCollection<ApplicationModel> AppsSource
{
get => _appsSource;
set => SetProperty(ref _appsSource, value, nameof(AppsSource));
}
private readonly DelegateCommand _onOpenLinkCommand = null;
public ICommand OnOpenLinkCommand => _onOpenLinkCommand;
private void OnOpenLink(object commandParameter)
{
ApplicationModel app = commandParameter as ApplicationModel;
if (app != null)
{
//Your code here
}
}
}
如果您需要实现DelegateCommand类。
这是同时在ListBox
和上完成的行为ListView
。
public class ItemDoubleClickBehavior : Behavior<ListBox>
{
#region Properties
MouseButtonEventHandler Handler;
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseDoubleClick += Handler = (s, e) =>
{
e.Handled = true;
if (!(e.OriginalSource is DependencyObject source)) return;
ListBoxItem sourceItem = source is ListBoxItem ? (ListBoxItem)source :
source.FindParent<ListBoxItem>();
if (sourceItem == null) return;
foreach (var binding in AssociatedObject.InputBindings.OfType<MouseBinding>())
{
if (binding.MouseAction != MouseAction.LeftDoubleClick) continue;
ICommand command = binding.Command;
object parameter = binding.CommandParameter;
if (command.CanExecute(parameter))
command.Execute(parameter);
}
};
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseDoubleClick -= Handler;
}
#endregion
}
这是用于查找父级的扩展类。
public static class UIHelper
{
public static T FindParent<T>(this DependencyObject child, bool debug = false) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
if (parentObject is T parent)
return parent;
else
return FindParent<T>(parentObject);
}
}
用法:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:coreBehaviors="{{Your Behavior Namespace}}"
<ListView AllowDrop="True" ItemsSource="{Binding Data}">
<i:Interaction.Behaviors>
<coreBehaviors:ItemDoubleClickBehavior/>
</i:Interaction.Behaviors>
<ListBox.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding YourCommand}"/>
</ListBox.InputBindings>
</ListView>