Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF - Binding an ObservableCollection Dependency Property within a UserControl

I have a control

class DragGrid : Grid { ... }

which inherits from the original grid and enables dragging and resizing its child elements. I need to bind a custom DP named WorkItemsProperty to an observable collection of type WorkItem (implements INotifyPropertyChanged). Each element in the grid is bound to a collection item.

Whenever the user adds a new item dynamically at runtime (the items cannot be declared in XAML!), or deletes an item from that collection, the WorkItems DP on the DragGrid should be updated, and the children in the grid (where each child represents a WorkItem collection item).

My question is how does the DP notify the control about which child element in the grid must be removed, changed ('change' means user dragged an element, or resized it with the mouse) or added, and how would I identify which one of the existing children is the one that needs to be deleted or changed. I understand that this is where the DependencyPropertyChangedCallback comes in. But that only gets called when the DP property is set anew, not when something inside the collection changes (like add, remove item). So in the end, does the DragGrid control somehow need to subscribe to the CollectionChanged event? At what point would I hook up the event handler for that?

*EDIT:: The reason for using a Grid in the first place is because I want to be able to maintain a minimum delta for when the user drags or resizes the control in the Grid. A control represents a time span, and each grid column represents 15min (which is the minimum value). Doing this in a Canvas with Thumbs was difficult and buggy. Implementing a DragGrid solved my user interaction problems. Also, a Canvas is not scalable, so the time spans would have to recalculated all the time. With the Grid, I don't have the problem because the columns tell me the time no matter the size.**

like image 604
John Avatar asked May 05 '10 07:05

John


1 Answers

In answer to your actual question:

You should add a DepencyPropertyChanged handler, as you mentioned. In this handler, you should add an event handler to the CollectionChanged property on the new collection and remove the handler from the old collection, like this:

    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
    }

As gehho explained, it sounds like you are not using the Grid class as originally intended, although you may be too far into development to want to start over at this point. Classes derived from Panel are actually intended only to visually draw/arrange their children, rather than manipulating and enhancing them. Check out ItemsControl and the WPF Content Model to learn more.

like image 51
Josh G Avatar answered Nov 07 '22 07:11

Josh G