在使用MVVM的WPF應用程序中,我有一個帶有listview項目的usercontrol。在運行時,它將使用數據綁定來填充對象集合的列表視圖。使用MVVM從WPF ListView項目中觸發雙擊事件
將雙擊事件附加到列表視圖中的項目的正確方法是什麼,以便當列表視圖中的項目被雙擊時,視圖模型中的相應事件被觸發並且具有對被點擊的項目的引用?
它如何以乾淨的MVVM方式完成,即View中沒有代碼?
在使用MVVM的WPF應用程序中,我有一個帶有listview項目的usercontrol。在運行時,它將使用數據綁定來填充對象集合的列表視圖。使用MVVM從WPF ListView項目中觸發雙擊事件
將雙擊事件附加到列表視圖中的項目的正確方法是什麼,以便當列表視圖中的項目被雙擊時,視圖模型中的相應事件被觸發並且具有對被點擊的項目的引用?
它如何以乾淨的MVVM方式完成,即View中沒有代碼?
我喜歡用Attached Command Behaviors和Commands。 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傭工。
您可以使用Caliburn的Action特性將事件映射到ViewModel上的方法。假設你在你的ViewModel
有ItemActivated
方法,那麼相應的XAML會是什麼樣子:
<ListView x:Name="list"
Message.Attach="[Event MouseDoubleClick] = [Action ItemActivated(list.SelectedItem)]" >
詳情您可以檢查卡利的文檔和示例。
請後面的代碼不是一件壞事。不幸的是,WPF社區中的很多人都犯了這個錯誤。
MVVM不是消除背後代碼的模式。它是將邏輯部分(工作流)中的視圖部分(外觀,動畫等)分開。此外,您可以對邏輯部分進行單元測試。
我知道有足夠的場景需要在後面編寫代碼,因爲數據綁定不是一切的解決方案。在你的場景中,我會在文件後面的代碼中處理DoubleClick事件,並將此調用委託給ViewModel。使用代碼,同時仍能滿足MVVM分離
示例應用程序可以在這裏找到:
WPF應用程序框架(WAF) - http://waf.codeplex.com
那麼說,我拒絕使用所有的代碼和一個額外的DLL只是做一個雙擊! – 2009-10-30 01:51:49
這隻使用綁定的東西給我一個真正的頭痛。這就像被要求用1隻手臂編碼,1隻眼睛在眼罩上,並站在1只腿上。雙擊應該很簡單,我不明白所有這些額外的代碼是值得的。 – Echiban 2010-04-25 06:31:33
我意識到,這種討論是一歲,但。 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();
}
雖然這種方法應該可以用任何你打開新視圖的方法。
我發現了一個非常簡單和乾淨的方式來使用Blend SDK Event觸發器完成此操作。清潔MVVM,可重複使用且不存在代碼隱藏。
你可能已經有這樣的事情:
<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">
現在有喜歡本作的ListViewItem的一個控件模板,如果你還沒有使用一個:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}" />
</ControlTemplate>
</Setter.Value>
</Setter>
的GridViewRowPresenter將是視覺構成列表行元素的所有元素的「內部」根。現在,我們可以插入一個觸發那裏通過InvokeCommandAction尋找MouseDoubleClick路由事件,並呼籲這樣的命令:
<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開始與網格),你也可以把在那裏觸發。
不幸的是,MouseDoubleClick事件不是從每個可視元素(它們來自Controls,但不是來自FrameworkElements)生成的。一種解決方法是從EventTrigger派生類,尋找MouseButtonEventArgs具有2的ClickCount的這有效地過濾掉所有的非MouseButtonEvents和所有MoseButtonEvents與ClickCount的!= 2
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.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>
我看到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和它的文本綁定,我不知道我可以繞過只在一個位置聲明。
我希望這可以幫助別人!
+1我發現這是我的首選解決方案時使用複合應用程序指導WPF(棱鏡)。 – 2010-02-01 18:26:01
命名空間'acb:'代表sampleabove中代表什麼? – 2010-07-18 08:44:35
@NamGiVU`acb:`= AttachedCommandBehavior。代碼可以在答案 – Rachel 2011-10-14 11:58:54