2009-06-23 159 views
92

在使用MVVM的WPF應用程序中,我有一個帶有listview項目的usercontrol。在運行時,它將使用數據綁定來填充對象集合的列表視圖。使用MVVM從WPF ListView項目中觸發雙擊事件

將雙擊事件附加到列表視圖中的項目的正確方法是什麼,以便當列表視圖中的項目被雙擊時,視圖模型中的相應事件被觸發並且具有對被點擊的項目的引用?

它如何以乾淨的MVVM方式完成,即View中沒有代碼?

回答

44

我喜歡用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傭工。

+1

+1我發現這是我的首選解決方案時使用複合應用程序指導WPF(棱鏡)。 – 2010-02-01 18:26:01

+1

命名空間'acb:'代表sampleabove中代表什麼? – 2010-07-18 08:44:35

+0

@NamGiVU`acb:`= AttachedCommandBehavior。代碼可以在答案 – Rachel 2011-10-14 11:58:54

4

您可以使用Caliburn的Action特性將事件映射到ViewModel上的方法。假設你在你的ViewModelItemActivated方法,那麼相應的XAML會是什麼樣子:

<ListView x:Name="list" 
    Message.Attach="[Event MouseDoubleClick] = [Action ItemActivated(list.SelectedItem)]" > 

詳情您可以檢查卡利的文檔和示例。

73

請後面的代碼不是一件壞事。不幸的是,WPF社區中的很多人都犯了這個錯誤。

MVVM不是消除背後代碼的模式。它是將邏輯部分(工作流)中的視圖部分(外觀,動畫等)分開。此外,您可以對邏輯部分進行單元測試。

我知道有足夠的場景需要在後面編寫代碼,因爲數據綁定不是一切的解決方案。在你的場景中,我會在文件後面的代碼中處理DoubleClick事件,並將此調用委託給ViewModel。使用代碼,同時仍能滿足MVVM分離

示例應用程序可以在這裏找到:

WPF應用程序框架(WAF) - http://waf.codeplex.com

+4

那麼說,我拒絕使用所有的代碼和一個額外的DLL只是做一個雙擊! – 2009-10-30 01:51:49

+4

這隻使用綁定的東西給我一個真正的頭痛。這就像被要求用1隻手臂編碼,1隻眼睛在眼罩上,並站在1只腿上。雙擊應該很簡單,我不明白所有這些額外的代碼是值得的。 – Echiban 2010-04-25 06:31:33

5

我意識到,這種討論是一歲,但。 NET 4,對這個解決方案有什麼想法嗎?我絕對同意,MVVM的重點不是消除文件背後的代碼。我也非常強烈地認爲,僅僅因爲事情很複雜,並不意味着它更好。以下是我放在後面的代碼:

private void ButtonClick(object sender, RoutedEventArgs e) 
    { 
     dynamic viewModel = DataContext; 
     viewModel.ButtonClick(sender, e); 
    } 
4

我發現它更簡單的鏈接時,視圖創建命令:

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(); 
} 

雖然這種方法應該可以用任何你打開新視圖的方法。

12

我發現了一個非常簡單和乾淨的方式來使用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> 
53

我能夠得到這個與.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> 
1

我看到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和它的文本綁定,我不知道我可以繞過只在一個位置聲明。

我希望這可以幫助別人!