2011-05-23 60 views
1

我想通過一個簡單的例子來掌握WPF數據綁定的概念,但似乎我沒有完全明白它的全部意義。WPF數據綁定 - 我錯過了什麼?

該示例是級聯下拉列表中的一個;在XAML如下:

<Window x:Class="CascadingDropDown.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="496" Width="949" Loaded="Window_Loaded"> 
    <Grid> 
     <ComboBox Name="comboBox1" ItemsSource="{Binding}" DisplayMemberPath="Key" SelectionChanged="comboBox1_SelectionChanged" /> 
     <ComboBox Name="comboBox2" ItemsSource="{Binding}" DisplayMemberPath="Name" /> 
    </Grid> 
</Window> 

這是形式的代碼:

public partial class MainWindow : Window 
{ 
    private ObservableCollection<ItemA> m_lstItemAContext = new ObservableCollection<ItemA>(); 
    private ObservableCollection<ItemB> m_lstItemBContext = new ObservableCollection<ItemB>(); 
    private IEnumerable<ItemB> m_lstAllItemB = null; 

    public MainWindow() 
    { 
     InitializeComponent(); 

     this.comboBox1.DataContext = m_lstItemAContext; 
     this.comboBox2.DataContext = m_lstItemBContext; 
    } 

    private void Window_Loaded(object sender, RoutedEventArgs e) 
    { 
     var lstItemA = new List<ItemA>() { new ItemA("aaa"), new ItemA("bbb"), new ItemA("ccc") }; 
     var lstItemB = new List<ItemB>() { new ItemB("aaa", "a11"), new ItemB("aaa", "a22"), new ItemB("bbb", "b11"), new ItemB("bbb", "b22") }; 

     initPicklists(lstItemA, lstItemB); 
    } 

    private void initPicklists(IEnumerable<ItemA> lstItemA, IEnumerable<ItemB> lstItemB) 
    { 
     this.m_lstAllItemB = lstItemB; 

     this.m_lstItemAContext.Clear(); 
     lstItemA.ToList().ForEach(a => this.m_lstItemAContext.Add(a)); 
    } 

    #region Control event handlers 

    private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     ComboBox ddlSender = (ComboBox)sender; 
     ItemA itemaSelected = (ItemA)ddlSender.SelectedItem; 

     var lstNewItemB = this.m_lstAllItemB.Where(b => b.KeyA == itemaSelected.Key); 

     this.m_lstItemBContext.Clear(); 
     lstNewItemB.ToList().ForEach(b => this.m_lstItemBContext.Add(b)); 
    } 

    private void comboBox2_?(object sender, ?EventArgs e) 
    { 
     // disable ComboBox if empty 
    } 
    #endregion Control event handlers 
} 

這些都是我的數據類:

class ItemA 
{ 
    public string Key { get; set; } 

    public ItemA(string sKey) 
    { 
     this.Key = sKey; 
    } 
} 

class ItemB 
{ 
    public string KeyA { get; set; } 

    public string Name { get; set; } 

    public ItemB(string sKeyA, string sName) 
    { 
     this.KeyA = sKeyA; 
     this.Name = sName; 
    } 
} 

因此,一旦某一項在comboBox1選擇,相應的項目應該顯示在comboBox2中。這與當前的代碼一起工作,但我不確定我的方式是重新填充各自的ObservableCollection是否理想。

我沒有能夠實現的是實際上對comboBox2底層集合的變化作出反應,例如當列表爲空時(即在comboBox1中選擇「ccc」時)停用控件。當然,我可以在ObservableCollection的CollectionChanged事件上使用一個事件處理函數,並且在這個例子中可以工作,但是在更復雜的情況下,ComboBox的DataContext可能會改變爲一個完全不同的對象(和可能會回來),這意味着雙重依賴 - 我總是不得不切換DataContext,而且還要來回切換事件處理程序。這對我來說似乎並不合適,但我可能只是在完全錯誤的軌道上。

基本上,我正在尋找的是一個事件觸發控件而不是基礎列表;而不是ObservableCollection宣佈「我的內容已經改變」,但ComboBox告訴我「我的項目發生了一些事情」。

我需要做什麼,或者我必須在哪裏糾正我對整個概念的理解?

回答

2

這裏的清潔劑(也許不是太大的優化)的方式來達致這,保持你的商業模式不變,並使用視圖模型和XAML唯一可能當:

視圖模型:

public class WindowViewModel : INotifyPropertyChanged 
{ 
    private ItemA selectedItem; 


    private readonly ObservableCollection<ItemA> itemsA = new ObservableCollection<ItemA>(); 
    private readonly ObservableCollection<ItemB> itemsB = new ObservableCollection<ItemB>(); 
    private readonly List<ItemB> internalItemsBList = new List<ItemB>(); 


    public WindowViewModel() 
    { 
     itemsA = new ObservableCollection<ItemA> { new ItemA("aaa"), new ItemA("bbb"), new ItemA("ccc") }; 
     InvokePropertyChanged(new PropertyChangedEventArgs("ItemsA")); 

     internalItemsBList = new List<ItemB> { new ItemB("aaa", "a11"), new ItemB("aaa", "a22"), new ItemB("bbb", "b11"), new ItemB("bbb", "b22") }; 

    } 

    public ObservableCollection<ItemA> ItemsA 
    { 
     get { return itemsA; } 
    } 

    public ItemA SelectedItem 
    { 
     get { return selectedItem; } 

     set 
     { 
      selectedItem = value; 

      ItemsB.Clear(); 
      var tmp = internalItemsBList.Where(b => b.KeyA == selectedItem.Key); 
      foreach (var itemB in tmp) 
      { 
       ItemsB.Add(itemB); 
      } 


      InvokePropertyChanged(new PropertyChangedEventArgs("SelectedItem")); 
     } 
    } 

    public ObservableCollection<ItemB> ItemsB 
    { 
     get { return itemsB; } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    public void InvokePropertyChanged(PropertyChangedEventArgs e) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, e); 
    } 
} 

代碼背後:

​​3210

和XAML:

<StackPanel> 
    <ComboBox Name="comboBox1" ItemsSource="{Binding ItemsA}" DisplayMemberPath="Key" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> 
    <ComboBox Name="comboBox2" ItemsSource="{Binding ItemsB}" DisplayMemberPath="Name"> 
     <ComboBox.Style> 
      <Style TargetType="{x:Type ComboBox}"> 
       <Setter Property="IsEnabled" Value="true"/> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding ItemsB.Count}" Value="0"> 
         <Setter Property="IsEnabled" Value="false"/> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 
     </ComboBox.Style> 
    </ComboBox> 
</StackPanel> 

複製粘貼這應該工作。

一些隨機的想法:

1)在WPF,儘量始終使用MVVM模式,從來沒有把代碼中的代碼隱藏文件的事件處理程序。對於用戶操作(如按鈕點擊),使用命令模式。對於無法使用命令的其他用戶操作,請儘量以「綁定方式」進行操作:由於您可以從VM屬性設置器中的視圖攔截事件,因此可以做很多事情(在您的示例中,我使用SelectedItem財產製定者)。 2)儘可能多地使用XAML。 WPF框架提供了一個非常強大的綁定和觸發器系統(在您的示例中,組合框的啓用不需要任何C#行)。

3)通過綁定使ObservableCollection被視圖模型暴露給視圖。它們也意味着與您可以在視圖模型中處理的CollectionChanged事件一起使用。利用這一點(在你的例子中,我使用虛擬機中的Observable集合玩這個遊戲應該發生的地方,集合中的任何變化都通過DataBinding反映在視圖中)。

希望這會有所幫助!

+0

謝謝;這正是我所需要的 - 在繼續研究之前,至少要深入研究MVVM的基礎知識。這看起來很理想,強大的數據綁定顯然消除了UI處理中的很多複雜性 – TeaDrivenDev 2011-05-24 22:25:28

0

您是否需要Keys集合?如果沒有我建議動態地從物品通過分組經由CollectionView創建它:

private ObservableCollection<object> _Items = new ObservableCollection<object>() 
{ 
    new { Key = "a", Name = "Item 1" }, 
    new { Key = "a", Name = "Item 2" }, 
    new { Key = "b", Name = "Item 3" }, 
    new { Key = "c", Name = "Item 4" }, 
}; 
public ObservableCollection<object> Items { get { return _Items; } } 
<StackPanel> 
    <StackPanel.Resources> 
     <CollectionViewSource x:Key="ItemsSource" Source="{Binding Items}"> 
      <CollectionViewSource.GroupDescriptions> 
       <PropertyGroupDescription PropertyName="Key"/> 
      </CollectionViewSource.GroupDescriptions> 
     </CollectionViewSource> 
    </StackPanel.Resources> 
    <StackPanel.Children> 
     <ComboBox Name="keyCb" ItemsSource="{Binding Source={StaticResource ItemsSource}, Path=Groups}" DisplayMemberPath="Name"/> 
     <ComboBox ItemsSource="{Binding ElementName=keyCb, Path=SelectedItem.Items}" DisplayMemberPath="Name"/> 
    </StackPanel.Children> 
</StackPanel> 

第一組合框示出了通過由Key -property,第二結合到分組生成的密鑰第一個組合框中的選定項目的子項目,顯示該項目的Name

另請參閱CollectionViewGroup reference,在拳頭CB我使用Name在第二個Items

當然,您也可以手動創建這些鍵組,並將項目嵌套在鍵對象中。

2

基本上,我正在尋找的是一個事件觸發控件而不是基礎列表;不是的ObservableCollection宣佈「我的內容發生了變化」,但組合框告訴我「的東西happenend我的項目」

,如果你想使用MVVM模式的話,我會說NO。不是控制器應該提供信息,而是你的視角模型應該。

採取ObservableCollection是一個很好的開始。在你的specail情況下,我會考慮創建一個ItemA的列表,我會添加ItemB的ItemB類型的新List屬性。

class ItemA 
{ 
public string Key { get; set; } 

public ItemA(string sKey) 
{ 
    this.Key = sKey; 
} 

public IEnumerable<ItemB> ListItemsB { get; set;} 

} 

我假設ItemA是父母嗎?

class ItemB 
{ 

public string Name { get; set; } 

public ItemB(string sName) 
{ 
    this.Name = sName; 
} 
} 

您有一個ItemA的集合,每個ItemA都有它自己的依賴ItemB的列表。

<ComboBox x:Name="cbo_itemA" ItemsSource="{Binding ListItemA}" DisplayMemberPath="Key"/> 
<ComboBox ItemsSource="{Binding ElementName=cbo_itemA, Path=SelectedItem.ListItemsB}" 
      DisplayMemberPath="Name" /> 
+0

+1雖然他可能沒有他的商業模式的選擇;) – Bruno 2011-05-23 11:38:07

+0

一個選擇總是創建一個包裝;) – blindmeis 2011-05-23 12:11:25