【问题标题】:WPF data binding - what am I missing?WPF 数据绑定 - 我错过了什么?
【发布时间】:2011-08-31 01:47:53
【问题描述】:

我正在尝试通过一个简单的示例来掌握 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 的方式是否理想。

我无法实现的实际上是对组合框2 的基础集合中的更改做出反应,例如,当列表为空时(即在组合框1 中选择“ccc”时)停用控件。

当然,我可以在 ObservableCollection 的 CollectionChanged 事件上使用事件处理程序,这在本示例中会起作用,但在更复杂的场景中,ComboBox 的 DataContext 可能会更改为完全不同的对象(并且可能back),这将意味着双重依赖——我不仅要切换 DataContext,还要来回切换事件处理程序。这对我来说似乎不对,但我可能只是在这个完全错误的轨道上。

基本上,我正在寻找的是在控件而不是底层列表上触发的事件;不是 ObservableCollection 宣布“我的内容已更改”,而是 ComboBox 告诉我“我的项目发生了一些事情”。

我需要做什么,或者我必须在哪里纠正我对整个概念的看法?

【问题讨论】:

    标签: wpf data-binding binding


    【解决方案1】:

    这是实现这一目标的更简洁(也许不是优化程度)的方法,保持您的业务模型不变,并且仅在可能的情况下使用 ViewModel 和 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);
        }
    }
    

    代码背后:

    public partial class Window1
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new WindowViewModel();
        }
    }
    

    和 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 属性设置器中的视图中拦截事件(在您的示例中,我使用 @ 987654324@属性设置器)。

    2) 尽可能多地使用 XAML。 WPF 框架提供了一个非常强大的绑定和触发系统(在你的例子中,组合框的启用不需要任何 C# 行)。

    3) ObservableCollection 被视图模型通过绑定暴露给视图。它们还旨在与您可以在视图模型中处理的 CollectionChanged 事件结合使用。充分利用这一点(在您的示例中,我在 VM 中使用 Observable 集合,应该在其中进行这种播放,并且集合中的任何更改都会通过 DataBinding 反映在视图中)。

    希望这会有所帮助!

    【讨论】:

    • 谢谢;这就是我所需要的——在继续之前,至少要研究一下 MVVM 的基础知识。这看起来确实是理想的方法,强大的数据绑定显然消除了 UI 处理中的许多复杂性
    【解决方案2】:

    您需要 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>
    

    第一个 ComboBox 显示通过 Key 属性分组生成的键,第二个绑定到第一个 ComboBox 中选定项的子项,显示该项的 Name

    另见CollectionViewGroup reference,在第一个CB中我使用Name在第二个Items

    当然,您也可以通过将项目嵌套在键对象中手动创建这些键组。

    【讨论】:

      【解决方案3】:

      基本上,我正在寻找的是在控件而不是底层列表上触发的事件;不是 ObservableCollection 宣布“我的内容已更改”,而是 ComboBox 告诉我“我的项目发生了一些事情”

      如果你想使用 MVVM 模式,那么我会说不。不是控件应该提供信息,而是您的视图模型应该提供信息。

      首先采用 ObservableCollection 是一个很好的步骤。在您的特殊情况下,我会考虑仅使用 ItemA 创建一个列表,然后将 ItemB 类型的新 List 属性添加到 ItemA。

      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" />
      

      【讨论】:

      • +1 尽管他可能无法选择他的商业模式 ;)
      • 一个选择总是创建一个包装器;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-02
      • 1970-01-01
      • 2019-11-18
      • 1970-01-01
      • 2011-01-14
      • 1970-01-01
      相关资源
      最近更新 更多