How to bind collection dependency property in UserControl

回眸只為那壹抹淺笑 提交于 2021-02-10 12:31:10

问题


This is not a duplicate! When I failed I tried to look to similar posts but without success. I cannot understand why OnUCItemsSourceChanged is not called? I'm pretty sure that I miss something simple but I cannot find it.

I have Window that contains UserControl1 which has attached collection property that is bound to Window's WindowCollection collection. I expect UserControl1.OnUCItemsSourceChanged to be called when I add items to WindowCollection. But it doesn't happen.

What I miss?

Window1.xaml.cs

public partial class Window1 : Window
{
    public ObservableCollection<long> WindowCollection { get; set; }
    public Window1()
    {
        InitializeComponent();

        DataContext = this;

        WindowCollection = new ObservableCollection<long>();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        WindowCollection.Add(1);
        WindowCollection.Add(2);
    }
}

Window1.xaml

<StackPanel>
    <uc:UserControl1 UCItemsSource="{Binding Path=WindowCollection}" />
    <Button Content="Refresh" Click="Button_Click" />
</StackPanel>

UserControl1.xaml.cs

public static readonly DependencyProperty UCItemsSourceProperty = DependencyProperty.Register("UCItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(null, new PropertyChangedCallback(OnUCItemsSourceChanged)));

public IEnumerable UCItemsSource
{
    get { return (IEnumerable)GetValue(UCItemsSourceProperty ); }
    set { SetValue(UCItemsSourceProperty , value); }
}

public ObservableCollection<MyItem> UCItems { get; set; }

private static void OnUCItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = d as UserControl1;
    var items = e.NewValue as ObservableCollection<long>;

    foreach (var item in items)
   {
         control.UCItems.Add(new MyItem(item));
   }
}

UserControl1.xaml

<ItemsControl ItemsSource="{Binding UCItems}" ... />

UPDATE This is link to my test project


回答1:


In this line:

<ItemsControl ItemsSource="{Binding UCItems}" ... />

Must be RelativeSource with FindAncestor, because UCItems located in UserControl:

UserControl

<ItemsControl ItemsSource="{Binding Path=UCItems,
                                    RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />

I cannot understand why OnUCItemsSourceChanged is not called?

If you add RelativeSource construction, then OnUCItemsSourceChanged causing at least once because PropertyChangedCallback triggered every time then you set new value for the dependency property:

Represents the callback that is invoked when the effective property value of a dependency property changes.

Since you once sets the value for dependency property here:

<uc:UserControl1 UCItemsSource="{Binding Path=WindowCollection}" />

I expect UserControl1.OnUCItemsSourceChanged to be called when I add items to WindowCollection.

For this is an ObservableCollection<T>.CollectionChanged event, in that contains the enumeration of acts performed on the collection:

Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.

For your case it will be something like this:

Version with CollectionChanged

MainWindow

public partial class MainWindow : Window
{
    public ObservableCollection<long> WindowCollection { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;

        WindowCollection = new ObservableCollection<long>();

        WindowCollection.Add(1);
        WindowCollection.Add(2);            
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        WindowCollection.Add(3);
        WindowCollection.Add(4);
    }
}

UserControl

public partial class UserControl1 : UserControl
{
    #region Public Section

    public ObservableCollection<long> UCItems { get; set; }
    public static UserControl1 control;

    #endregion

    public UserControl1()
    {
        InitializeComponent();
        UCItems = new ObservableCollection<long>();            
    }

    #region UCItemsSource Property

    public static readonly DependencyProperty UCItemsSourceProperty = DependencyProperty.Register("UCItemsSource", 
                                                                                                  typeof(IEnumerable), 
                                                                                                  typeof(UserControl1),
                                                                                                  new PropertyMetadata(null, new PropertyChangedCallback(OnUCItemsSourceChanged)));

    public IEnumerable UCItemsSource
    {
        get { return (IEnumerable)GetValue(UCItemsSourceProperty); }
        set { SetValue(UCItemsSourceProperty, value); }
    }

    #endregion

    private static void OnUCItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        control = d as UserControl1;
        var items = e.NewValue as ObservableCollection<long>;

        items.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged);
        AddItem(control, items);
    }

    private static void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        var items = sender as ObservableCollection<long>;
        control.UCItems.Clear();

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            AddItem(control, items);
        }
    }

    private static void AddItem(UserControl1 userControl, ObservableCollection<long> collection) 
    {
        if (collection.Count > 0)
        {
            foreach (var item in collection)
            {
                userControl.UCItems.Add(item);
            }
        }
    }
}

This project available in this link

Alternative version

This version is simpler and more correct. Here we just reference to UCItemsSource property that contain collection, also here RelativeSource justified:

UserControl

XAML

<Grid>
    <ItemsControl ItemsSource="{Binding Path=UCItemsSource, 
                                        RelativeSource={RelativeSource Mode=FindAncestor,
                                                                       AncestorType={x:Type UserControl}}}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

Code-behind

public partial class UserControl1 : UserControl
{
    #region Public Section

    public ObservableCollection<long> UCItems { get; set; }

    #endregion

    public UserControl1()
    {
        InitializeComponent();

        UCItems = new ObservableCollection<long>();
    }

    #region UCItemsSource Property

    public static readonly DependencyProperty UCItemsSourceProperty = DependencyProperty.Register("UCItemsSource", 
                                                                                                  typeof(IEnumerable), 
                                                                                                  typeof(UserControl1));                                                                                                      

    public IEnumerable UCItemsSource
    {
        get { return (IEnumerable)GetValue(UCItemsSourceProperty); }
        set { SetValue(UCItemsSourceProperty, value); }
    }

    #endregion
}



回答2:


Try this

private ObservableCollection<long> _windowCollection 
public ObservableCollection<long> WindowCollection 
{ 
   get { return _windowCollection; }
   set
   {
      _windowCollection = value;
      RaiseOnPropertyChange(() => WindowCollection);
   }
}


来源:https://stackoverflow.com/questions/22650599/how-to-bind-collection-dependency-property-in-usercontrol

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!