【问题标题】:WPF - Binding an ObservableCollection Dependency Property within a UserControlWPF - 在 UserControl 中绑定 ObservableCollection 依赖属性
【发布时间】:2010-05-05 07:53:25
【问题描述】:

我有控制权

类 DragGrid : 网格 { ... }

继承自原始网格并允许拖动和调整其子元素的大小。 我需要将一个名为 WorkItemsProperty 的自定义 DP 绑定到一个类型为 WorkItem 的可观察集合(实现 INotifyPropertyChanged)。网格中的每个元素都绑定到一个集合项。

每当用户在运行时动态添加一个新项目(这些项目不能在 XAML 中声明!),或从该集合中删除一个项目,DragGrid 上的WorkItems DP 应该更新,并且网格中的子项(其中每个孩子代表一个WorkItem 集合项)。

我的问题是 DP 如何通知控件网格中的哪个子元素必须删除更改(“更改”表示用户拖动了一个元素,或者用鼠标调整它的大小)或添加,以及我如何确定哪个现有的孩子是需要删除或更改的孩子。 我知道这是 DependencyPropertyChangedCallback 出现的地方。但是只有在重新设置 DP 属性时才会调用它,而不是当集合内的某些内容发生更改时(如添加、删除项目)。那么最后,DragGrid 控件是否需要以某种方式订阅 CollectionChanged 事件?在什么时候我会为此连接事件处理程序?

*编辑:: 首先使用 Grid 的原因是因为我希望能够在用户拖动或调整 Grid 中的控件大小时保持最小增量。一个控件代表一个时间跨度,每个网格列代表15分钟(这是最小值)。在 Canvas with Thumbs 中执行此操作既困难又麻烦。实现 DragGrid 解决了我的用户交互问题。此外,Canvas 不可扩展,因此必须始终重新计算时间跨度。使用网格,我没有问题,因为无论大小,列都会告诉我时间。**

【问题讨论】:

    标签: c# wpf data-binding observablecollection dependency-properties


    【解决方案1】:

    回答您的实际问题:

    您应该添加一个 DepencyPropertyChanged 处理程序,正如您所提到的。在此处理程序中,您应该向新集合的 CollectionChanged 属性添加一个事件处理程序,并从旧集合中删除该处理程序,如下所示:

        public ObservableCollection<WorkItem> WorkItems
        {
            get { return (ObservableCollection<WorkItem>)GetValue(WorkItemsProperty); }
            set { SetValue(WorkItemsProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for WorkItems.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WorkItemsProperty =
            DependencyProperty.Register("WorkItems", typeof(ObservableCollection<WorkItem>), typeof(DragGrid), new FrameworkPropertyMetadata(null, OnWorkItemsChanged));
    
        private static void OnWorkItemsChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            DragGrid me = sender as DragGrid;
    
            var old = e.OldValue as ObservableCollection<WorkItem>;
    
            if (old != null)
                old.CollectionChanged -= me.OnWorkCollectionChanged;
    
            var n = e.NewValue as ObservableCollection<WorkItem>;
    
            if (n != null)
                n.CollectionChanged += me.OnWorkCollectionChanged;
        }
    
        private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                // Clear and update entire collection
            }
    
            if (e.NewItems != null)
            {
                foreach (WorkItem item in e.NewItems)
                {
                    // Subscribe for changes on item
                    item.PropertyChanged += OnWorkItemChanged;
    
                    // Add item to internal collection
                }
            }
    
            if (e.OldItems != null)
            {
                foreach (WorkItem item in e.OldItems)
                {
                    // Unsubscribe for changes on item
                    item.PropertyChanged -= OnWorkItemChanged;
    
                    // Remove item from internal collection
                }
            }
        }
    
        private void OnWorkItemChanged(object sender, PropertyChangedEventArgs e)
        {
            // Modify existing item in internal collection
        }
    

    正如 gehho 解释的那样,听起来您并没有按照最初的预期使用 Grid 类,尽管您可能离开发太远而不想在此时重新开始。从 Panel 派生的类实际上只是为了在视觉上绘制/排列他们的孩子,而不是操纵和增强他们。查看ItemsControlWPF Content Model 了解更多信息。

    【讨论】:

    • 乔希,谢谢。在 collectionchanged 事件中,我无法添加到工作项的内部集合,因为 .NET 不允许这样做。我认为您在这里的意思是将控件添加到网格的 Children 集合中。
    • 不,实际上我的意思是将它添加到您的集合 + 网格的 Children 集合等中。我不确定您的控件是如何工作的,但很明显您正在努力管理一些集合项目。根据您的评论,听起来您正在管理 Grid.Children 集合,所以是的,将其添加到那里。
    • 理想情况下,您将获取一个 ItemsControl(或者可能从中获取一个自定义子类,如果没有更好地理解您的问题就不确定),并且您会将 WorkItems 设置为 ItemsControl 的子级。然后您将使用ItemsPanelTemplate 用于在 Grid 上显示项目。您将使用 DataTemplate 为工作项生成实际的视觉效果。项模板控件、容器控件(由 ItemsControl 生成)和 Grid 上的 DataBinding 将允许将项绘制在正确的位置。
    • 感谢 Josh,您的建议解决了我们的问题。它按预期工作!
    【解决方案2】:

    抱歉,对于您的具体自定义 Grid 问题,我没有解决方案,但我只有一个建议,您可以如何更轻松地做到这一点(我想,这是 WPF 设计人员的意思)。实际上,Grid 不是用于排列 items 的控件。它是一个Panel,它排列Controls。所以,我想,这就是您的解决方案遇到麻烦的(其中一个)原因。

    我会改用ItemsControl(例如ListBox)和Canvas 作为ItemsPanel

    <ListBox ItemsSource="{Binding WorkItemsProperty}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
    

    现在,您在WorkItem(或WorkItemViewModel)类中定义适当的属性,例如XPosYPos,它们将被数据绑定到Canvas.LeftCanvas.Top 属性,如下所示:

    <Style x:Key="WorkItemStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="Canvas.Left" Value="{Binding XPos, Mode=TwoWay}"/>
        <Setter Property="Canvas.Top" Value="{Binding YPos, Mode=TwoWay}"/>
    </Style>
    

    然后您可以通过分配ListBoxItemContainerStyle 属性来使用此项目样式:

    ItemContainerStyle="{StaticResource WorkItemStyle}"
    

    我不知道如何实现拖放的东西,因为我从来没有这样做过,但显然你已经为你的自定义Grid做了它,所以在@中使用它应该不是一个大问题987654344@ 也是。但是,如果您更新 WorkItem 的属性,它应该会自动重新定位元素。此外,如果您在集合 (WorkItemsProperty) 中添加/删除项目,它将自动添加/删除,因为 ListBox 是数据绑定到集合的。

    您可能需要根据您的情况更改您的WorkItemStyle。例如,如果 ListBox 在运行时调整大小,您可能必须使位置相对于容器的 (Canvas') 大小。因此,您需要MultiBinding 而不是简单的Binding。但那是另一回事了……

    现在,您可以决定是否仍然可以采用这种方法,或者您的Grid 是否即将完成并且您不愿意改变。我知道这很难,但在我看来,上述方法是更清洁(也更容易!)的一种!

    【讨论】:

      猜你喜欢
      • 2014-08-31
      • 1970-01-01
      • 2018-01-23
      • 2017-12-24
      • 2017-07-25
      • 1970-01-01
      • 2023-04-07
      • 2014-04-10
      • 1970-01-01
      相关资源
      最近更新 更多