WPF - 在UserControl中绑定ObservableCollection依赖项属性

时间:2010-05-05 07:53:25

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

我有一个控件

  

类DragGrid:网格   {   ...   }

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

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

我的问题是DP如何通知控件网格中的哪个子元素必须删除已更改('更改'表示用户拖动元素,或者用鼠标调整大小)或添加,我如何识别哪一个现有子项是需要删除或更改的子项。 我知道这是DependencyPropertyChangedCallback的用武之地。但是只有在重新设置DP属性时才会调用它,而不是在集合中的某些内容发生更改时(如add,remove item)。那么最后,DragGrid控件是否需要订阅CollectionChanged事件?我将在什么时候连接事件处理程序?

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

2 个答案:

答案 0 :(得分:17)

回答你的实际问题:

如您所述,您应该添加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了解详情。

答案 1 :(得分:1)

抱歉,我没有解决您的具体自定义Grid问题,但我只是建议您如何更容易地做到这一点(而且,我想,WPF设计师的意思如何)。实际上,Grid不是安排的控件。它是一个Panel来安排Controls。因此,我想,这是(其中一个)原因导致您的解决方案遇到问题。

我会使用的是ItemsControl(例如ListBox),CanvasItemsPanel

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

现在,您可以在WorkItem(或WorkItemViewModel)类中定义适当的属性,例如XPosYPos,这些属性将数据绑定到Canvas.Left和{ {1}}这样的属性:

Canvas.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> 的{​​{1}}属性来使用此项目样式:

ItemContainerStyle

我不知道如何实现拖放操作,因为我从未这样做过,但显然你已经为自定义ListBox完成了它,所以在使用它时不应该是一个大问题一个ItemContainerStyle="{StaticResource WorkItemStyle}" 。但是,如果更新Grid的属性,则应自动重新定位元素。此外,如果您在集合中添加/删除项目(ListBox),则会自动添加/删除项目,因为WorkItem与数据绑定到集合。

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

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