1

I have simple UserControl where is defined property ItemsSource

public static readonly DependencyProperty ItemsSourceProperty =
         DependencyProperty.Register("ItemsSource", typeof(Dictionary<string, object>), typeof(UserControl1), new FrameworkPropertyMetadata(null,
    new PropertyChangedCallback(UserControl1.OnItemsSourceChanged)));

public Dictionary<string, object> ItemsSource
    {
        get { return (Dictionary<string, object>)GetValue(ItemsSourceProperty); }
        set
        {
            SetValue(ItemsSourceProperty, value);
        }
    }

private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UserControl1 control = (UserControl1)d;
            control.DisplayInControl();
        }

I want to make this property update dynamically, but I am wondered why OnItemsSourceChanged doesn't fired every time when something happend with ItemsSource. So I am upset. I've tried Custom ItemsSource property for a UserControl but this doesn't help or I've written bad newValueINotifyCollectionChanged_CollectionChanged function


My control is from this post CodeProject


My Code : UserControl XAML - http://pastie.org/10606317

UserControl CodeBehind - http://pastie.org/10606322

Control Usage -

<controls:MultiSelectComboBox SelectedItems="{Binding SelectedCategories, Mode=TwoWay}" Grid.Column="0" Grid.Row="0" x:Name="CategoriesFilter" DefaultText="Category" ItemsSource="{Binding Categories }" Style="{StaticResource FiltersDropDowns}"/>

Update : I've made small step to solution. I have next style :

<Style.Triggers>
            <DataTrigger Binding="{Binding ItemsSource, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
            <DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
        </Style.Triggers>

which I apply to my control (make control disabled if no itemSource). As I update control source on click, I see that control becomes enabled, so ItemsSource aren't empty (from start it is). So problem now is just in Redrawing control content if I understand this behaviour correctly.

Community
  • 1
  • 1
demo
  • 6,038
  • 19
  • 75
  • 149
  • Define your DP in static constructor of your class. – AnjumSKhan Dec 03 '15 at 04:04
  • public static readonly DependencyProperty ItemsSourceProperty; static YourClassName() { ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(Dictionary), typeof(UserControl1), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(UserControl1.OnItemsSourceChanged))); } – AnjumSKhan Dec 03 '15 at 15:07
  • @AnjumSKhan - after I tried it, i got exception - `Additional information: 'ItemsSource' property was already registered by 'UserControl1'.` – demo Dec 03 '15 at 15:21
  • upload your code where you are using it. – AnjumSKhan Dec 04 '15 at 10:45
  • @AnjumSKhan, please check update. Hope this is fine for you – demo Dec 04 '15 at 12:14
  • When you say "something happend with ItemsSource" are you referring to an item added or removed from the source? Or are looking for an event to fire whenever an item in the source is changed? An [ObservableCollection](https://msdn.microsoft.com/en-us/library/ms668604.aspx) as mentioned in the answers below should already handle the former. – Mike Guthrie Dec 04 '15 at 14:42
  • @MikeGuthrie, what I do with ItemsSource - it is `Clear` and `Add`. I've tried with ObservableCollection - but still on UI content of my control is unchanged – demo Dec 04 '15 at 14:52
  • @demo I ran your code, and OnItemsSourceChanged function is executing as expected. – AnjumSKhan Dec 05 '15 at 17:10
  • @AnjumSKhan The problem isn't really the `OnItemsSourceChanged`, rather that the collection he's binding is ultimately entirely different than the one used by the rendered control -- see my answer, below, and check [the code-behind link](http://pastie.org/10606322), line 164. Also, mind deleting some comments to remove the clutter? – Mike Guthrie Dec 05 '15 at 23:15

3 Answers3

1

If you have a dictionary as your data type, and you add or remove a value from the dictionary, then your property did not actually change. This event will only fire if you have actually set this property to reference a different dictionary.

If you need wpf to automatically detect if an item is added or removed from the dictionary, you should use an ObservableCollection.

user3308241
  • 344
  • 3
  • 10
  • I've tried `ObservableCollection` but still `ItemsSource` of my control is the same – demo Dec 03 '15 at 11:59
  • Just for clarification, are you trying to update your control when you add or remove an item? – user3308241 Dec 03 '15 at 17:10
  • Also, would it be possible to post the xaml for your user control at the bottom of your question? Maybe that would clarify what you need to do to solve this problem. – user3308241 Dec 03 '15 at 17:12
  • http://www.codeproject.com/Articles/563862/Multi-Select-ComboBox-in-WPF My control is from this post – demo Dec 04 '15 at 10:21
1
  1. Replace Dictionary with ObservableCollection, Dictionary won't fire the propertyChanged event when add, update, delete an item.
  2. Write your own Dictionary to fire the propertychanged event manually.
Willis.Hu
  • 265
  • 1
  • 2
  • 14
0

I see that the problem is that you are building a new ItemsSource for the ComboBox control within your custom UserControl, and that you are expecting that MultiSelectCombo.ItemsSource to stay synced with your UserControl1.ItemsSource. This can't happen when simply binding a Dictionary, and it won't happen even when binding an ObservableCollection -- unless you explicitly handle it.

To accomplish what you are after, first you will need ItemsSource of your custom control to be of a type that does notify us of collection changes, such as the ObservableCollection (which I'll use for this example, as you've done in your links above). Then, you'll need to update the ItemsSource of your ComboBox within your control, to keep them in sync.

private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (MultiSelectComboBox)d;

    // Since we have to listen for changes to keep items synced, first check if there is an active listener to clean up.
    if (e.OldValue != null)
    {
        ((ObservableCollection<Node>)e.OldValue).CollectionChanged -= OnItemsSource_CollectionChanged;
    }

    // Now initialize with our new source.
    if (e.NewValue != null)
    {
        ((ObservableCollection<Node>)e.NewValue).CollectionChanged += OnItemsSource_CollectionChanged;
        control.DisplayInControl();
    }
}

private void OnItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    DisplayInControl();
}

Now, that said, the solution above is for a more generic case, where you might need to do something with the ItemsSource given to your custom control before passing it onto your MultiSelectCombo.ItemsSource. In your current case, however, you are simply building a collection of Node to exactly match the given collection of Node. If that's guaranteed to be the case, your solution can be much, much simpler:

private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (MultiSelectComboBox)d;
    control.MultiSelectCombo.ItemsSource = (IEnumerable)e.NewValue;
}

Just let the wrapped ComboBox use the ItemsSource given to your custom control.

Mike Guthrie
  • 4,029
  • 2
  • 25
  • 48
  • `((ObservableCollection)e.OldValue).CollectionChanged += OnItemsSource_CollectionChanged;` should here be `e.NewValue` ? – demo Dec 04 '15 at 16:34
  • I've tried "simpler" sample, but this doesn't solve my problem. Don't know why... – demo Dec 04 '15 at 16:43
  • @demo Yeah, sorry, that was an error from copy-paste. You are right, should be `e.NewValue`. Does either solution solve your issue? Seems like you should be able to just pass along the `ItemsSource` of `UserControl` to `ComboBox` and still have it work. You are sure that items are added to the correct source instance? – Mike Guthrie Dec 04 '15 at 18:21
  • Thank you, Mike. Your answer help me :). Problem was in way, how I modify my `ObservableCollection`. Firstly I've added elements like `Types.Add(element)` in loop. But now I've tried next `Types = new ObservableCollection(list)` where list is container for elements that I've added one by one. So now it works. Is it ok to do like this? – demo Dec 07 '15 at 12:31
  • @demo That's fine to do to initialize the control, so long as you understand that adding elements to `list` after your `Types = new ObservableCollection(list)` call are not going to include them in the rendered conrol. – Mike Guthrie Dec 07 '15 at 14:49
  • Well, I've found one problem with everytime initializing - It can't save checked elements on previous step – demo Dec 23 '15 at 15:41