2012-02-04 22 views
12

所以有人建議使用WPF TreeView,我想:「是的,這似乎是正確的方法。」現在,幾小時後,我簡直無法相信使用這種控制是多麼困難。通過一系列研究,我能夠使TreeView`控件正常工作,但我無法找到將所選項目傳遞給視圖模型的「正確」方法。我不需要從代碼中設置選定的項目;我只需要我的視圖模型就可以知道用戶選擇了哪個項目。得到選擇使用MVVM的TreeViewItem

到目前爲止,我有這個XAML,它本身並不是很直觀。這是所有的UserControl.Resources標籤內:

<CollectionViewSource x:Key="cvs" Source="{Binding ApplicationServers}"> 
    <CollectionViewSource.GroupDescriptions> 
     <PropertyGroupDescription PropertyName="DeploymentEnvironment"/> 
    </CollectionViewSource.GroupDescriptions> 
</CollectionViewSource> 

<!-- Our leaf nodes (server names) --> 
<DataTemplate x:Key="serverTemplate"> 
    <TextBlock Text="{Binding Path=Name}"/> 
</DataTemplate> 

<!-- Note: The Items path refers to the items in the CollectionViewSource group (our servers). 
      The Name path refers to the group name. --> 
<HierarchicalDataTemplate x:Key="categoryTemplate" 
          ItemsSource="{Binding Path=Items}" 
          ItemTemplate="{StaticResource serverTemplate}"> 
    <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/> 
</HierarchicalDataTemplate> 

而這裏的樹視圖:

<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Source={StaticResource cvs}, Path=Groups}" 
       ItemTemplate="{StaticResource categoryTemplate}"> 
      <Style TargetType="TreeViewItem"> 
       <Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/> 
      </Style> 
     </TreeView> 

這正確的環境(開發,QA,PROD)顯示服務器。不過,我已經找到了各種方法來獲得選定的項目,許多方法是複雜和困難的。是否有簡單的方式將選定的項目獲取到我的視圖模型?

注意:TreeView`上有一個SelectedItem屬性,但它是隻讀的。對我來說令人沮喪的是,只讀是好的;我不想通過代碼來改變它。但是我不能使用它,因爲編譯器抱怨它是隻讀的。

還有一個看似優雅的建議,做這樣的事情:

<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" /> 

我問這個問題:「您的視圖模型如何能得到這個信息,我拿到ContentPresenter持有所選擇的項目,但是我們如何將這一點轉移到視圖模型?「但目前還沒有答案。

因此,我的整體問題是:「是否有簡單的方法將選定的項目獲取到我的視圖模型?」

回答

27

,做你想做,你可以修改TreeViewItemContainerStyle什麼:

<TreeView> 
    <TreeView.ItemContainerStyle> 
    <Style TargetType="TreeViewItem"> 
     <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/> 
    </Style> 
    </TreeView.ItemContainerStyle> 
</TreeView> 

您的視圖模型(視圖模型樹中的每個項目),則必須公開一個布爾IsSelected屬性。

如果你希望能夠當一個特定的TreeViewItem控制擴展,你可以使用一個setter該屬性太:然後

<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> 

您的視圖模型已經揭露一個布爾IsExpanded屬性。

請注意,這些屬性可以雙向工作,因此如果用戶在樹中選擇節點,則視圖模型的IsSelected屬性將設置爲true。另一方面,如果在視圖模型上將IsSelected設置爲true,則將選擇該視圖模型樹中的節點。同樣與擴大。

如果您沒有樹中每個項目的視圖模型,那麼您應該得到一個。沒有視圖模型意味着你正在使用你的模型對象作爲視圖模型,但爲了這個工作,這些對象需要一個IsSelected屬性。

要在父視圖模型暴露的SelectedItem屬性(綁定到TreeView的一個和具有子視圖模型的集合)可以實現這樣的:

public ChildViewModel SelectedItem { 
    get { return Items.FirstOrDefault(i => i.IsSelected); } 
} 

如果您不想跟蹤樹上每個單獨項目的選擇,則仍然可以使用TreeView上的SelectedItem屬性。然而,爲了能夠做到「MVVM風格」,你需要使用Blend行爲(可用於各種NuGet包 - 搜索「混合交互性」)。

在這裏,我增加了一個EventTrigger,每次都會調用命令在樹中選擇的項目變化:

<TreeView x:Name="treeView"> 
    <i:Interaction.Triggers> 
    <i:EventTrigger EventName="SelectedItemChanged"> 
     <i:InvokeCommandAction 
     Command="{Binding SetSelectedItemCommand}" 
     CommandParameter="{Binding SelectedItem, ElementName=treeView}"/> 
    </i:EventTrigger> 
    </i:Interaction.Triggers> 
</TreeView> 

您必須對TreeViewDataContext返回一個ICommand添加屬性SetSelectedItemCommand。當樹視圖的選定項目更改Execute方法時,將使用所選項目作爲參數調用命令。創建命令最簡單的方法可能是使用DelegateCommand(谷歌它獲得一個實現,因爲它不是WPF的一部分)。

一個允許雙向綁定而沒有笨拙命令的更好的替代方法是在Stack Overflow中使用Steve Greatrex提供的BindableSelectedItemBehavior

+0

但是視圖模型沒有綁定到IsSelected?它是如何獲得價值的? – 2012-02-04 18:19:29

+0

而我的意思是所選項目的價值。我不想看看是否選擇了某些東西,我想知道所選項目的價值。 – 2012-02-04 18:28:20

+0

所以,我只注意到你寫了這個:「(樹中每個項目的視圖模型)」。我沒有樹中每個項目的視圖模型。樹中的每個項目都是* one *視圖模型中* one *列表中的項目。 – 2012-02-04 18:32:02

4

我可能會使用SelectedItemChanged事件來設置虛擬機上的相應屬性。

+0

我不需要使用代碼隱藏來處理事件嗎?我試圖純粹這一點,迄今爲止我還沒有代碼隱藏代碼。 – 2012-02-04 18:20:12

+2

@BobHorn:不一定,但人們對代碼背後太癡迷了,這並不是什麼大不了的事情...... – 2012-02-04 18:43:00

+0

是的,你可能是對的,但是如果我打算讓這棵treeview垃圾是突破點...大聲笑。 – 2012-02-04 18:48:03

1

基於Martin的回答,我做了一個簡單的應用程序,展示瞭如何應用所提出的解決方案。

示例代碼使用Cinch V2框架來支持MVVM,但它可以很容易地更改爲使用您的偏好框架。

對於那些有興趣,here is the code在GitHub上

希望它能幫助。

0

有點遲到了,但對於那些現在誰碰到這個來了,我的解決辦法是:

  1. 增加提及「System.Windows.Interactivity」
  2. 下面的代碼添加到您的TreeView元件。 <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>

這將允許您使用標準的MVVM的ICommand結合,而無需使用左右背後的代碼或一些長篇大論的工作訪問的SelectedItem。