1

I have issue with custom DependencyProperty in my control. Let me explain:

I have control with list of the checkable items. I need property binding to the IEnumerable SelectedItems. The logic of SelectedItemsProperty filling is inside of control, so it is not just simple binding. Here is code behind of my control:

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IEnumerable<object>), typeof(ButtonColumnFilter));

    public IEnumerable<object> SelectedItems
    {
      get { return (IEnumerable<object>)GetValue(SelectedItemsProperty); }
      set { Debug.WriteLine("SelectedItems has been set in ButtonColumnFilter - Count:{0}", value?.Count());
            SetValue(SelectedItemsProperty, value);
          }
     }

Here I get Debug message with correct number of selected items, so my logic inside of control is working well. The property in XAML is binded here:

    <MESControls:ButtonColumnFilter CanSearch="True" CanSort="False" DisplayMemberPath="DisplayName"
ItemsSource="{Binding Path=FilterAdapter.Drivers, Mode=OneWay, IsAsync=True}"
OnApplyFilter="Drivers_ApplyFilter"
SelectedItems="{Binding Path=SelectedFilterDrivers, Mode=TwoWay}"/>

And property is defined in my code here:

public IEnumerable<Driver> SelectedFilterDrivers
        {
            get => _SelectedFilterDrivers;
            set
            {

                Debug.WriteLine("SelectedFilterDrivers has been set in PlannerFilterAdapter - Count: {0}", value?.Count());
                if (_SelectedFilterDrivers != value)
                {

                    _SelectedFilterDrivers = value;
                    NotifyPropertyChanged("SelectedFilterDrivers");
                }
            }
        }

!!! But here I get Debug message 'value == null' !!!

The binding is working well, and property set is called correctly in time i suppose. The weird is, that SetValue inside control has correct value, but outside in code the propert set value is null.

What can be wrong? Thanks.

UPDATE:

Here is code that changes SelectedItems inside control:

private void CheckableItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (e.PropertyName == "IsChecked" && !_IsChecking)
            {
                _IsChecking = true;

                CheckableItem item = sender as CheckableItem;

                if(item.IsChecked == true && InnerItemsSource.Where(ci=>ci.Item.ToString() != SELECTALL).All(ci=>ci.IsChecked == true))
                {
                    InnerItemsSource.Single(ci => ci.Item.ToString() == SELECTALL).IsChecked = true;
                    SelectAllIsSelected = true;
                }
                else if (InnerItemsSource.Where(ci => ci.Item.ToString() != SELECTALL).All(ci=>ci.IsChecked == true) || InnerItemsSource.Where(ci => ci.Item.ToString() != SELECTALL).All(ci => ci.IsChecked == false))
                {
                    InnerItemsSource.Single(ci => ci.Item.ToString() == SELECTALL).IsChecked = false;
                    SelectAllIsSelected = false;
                } 
                else 
                {
                    InnerItemsSource.Single(ci => ci.Item.ToString() == SELECTALL).IsChecked = null;
                    SelectAllIsSelected = false;
                }

                SelectedItems = CheckedItems;

                NotifyPropertyChanged("IsFilterUsed");
                NotifyPropertyChanged("FilterIcon");


            }

            _IsChecking = false;
        }

public IEnumerable<object> CheckedItems
        {
            get
            {
                return InnerItemsSource?.Where(ci => ci.IsChecked == true && ci.Item.ToString() != SELECTALL).Select(ci => ci.Item);
            }

        }

But as I written, the property set inside control receive correct value and I guess correct type. The return value of CheckedItems is IEnumerable'<object>'.

Thanks for help.

value is IEnumerable<Driver>

Call stack situation

Here is visible count of the collection just one step back in the call stack Here is visible count of the collection just one step back in the call stack

Next step of call stack - breakpoint - value is null Next step of call stack - breakpoint - value is null The true is, that between theses steps is some external code - but I didn't get any error neither warning The true is, that between theses steps is some external code - but I didn't get any error neither warning

UPDATE - some progress

I tried change public IEnumerable<Driver> SelectedFilterDrivers to

public IEnumerable<object> SelectedFilterDrivers
        {
            get => _SelectedFilterDrivers;
            set
            {

                Debug.WriteLine("SelectedFilterDrivers has been set in PlannerFilterAdapter - Count: {0}", value?.Count());
                if (_SelectedFilterDrivers != value)
                {

                    _SelectedFilterDrivers = (IEnumerable<Driver>)value;
                    NotifyPropertyChanged("SelectedFilterDrivers");
                }
            }
        }

And I receive correct number of items. So there is an issue during retyping of items in collection. So I need to kick forward with the better way, how to keep ability for SelectedItems to use generic IEnumerable<object>

Important is, that IEnumerable<object> is working well with simple binding (in this case ItemsSource - true is, that it si just OneWay binding)

Miroslav Endyš
  • 154
  • 1
  • 8
  • Is there any code in your control that actually changes the `SelectedItems` property, i.e. assigns a new collection instance, or do you just add/remove items? In the latter case, bind the property to an ObservableCollection. – Clemens Jan 07 '19 at 10:12
  • When do you set your property inside control? – Dmitry Jan 07 '19 at 10:12
  • Requested code added. I always add new instance of collection. – Miroslav Endyš Jan 07 '19 at 10:28
  • And is it assignment compatible with the binding source property, i.e. is it a collection of Driver objects? – Clemens Jan 07 '19 at 10:31
  • You can put a conditional breakpoint (condition: `value==null`) into your SelectedFilterDrivers setter and see where this call comes from. – Klaus Gütter Jan 07 '19 at 10:39
  • @Clemens I provided picture with the values passed to the property. It is IEnumerable of drivers. – Miroslav Endyš Jan 07 '19 at 10:45
  • @KlausGütter It always receive null even if the control passed via SetValue IEnumerable collection. And It is called immediately after control SetValue for DependencyProperty SelectedItems. – Miroslav Endyš Jan 07 '19 at 10:47
  • What if you force enumeration of the collection by adding `.ToList()` after `Select(ci => ci.Item)`? – Clemens Jan 07 '19 at 10:53
  • @Clemens .ToList() does not work. Same result. – Miroslav Endyš Jan 07 '19 at 10:56
  • Is there any data binding error message in the Output Window in Visual Studio? – Clemens Jan 07 '19 at 11:01
  • @Clemens There is no error, no warning. I posted the pictures for better imagination. As you can see, SetValue contains collection of 6 items and it is one step back in call stack before breakpoint, where the property set value is null. The true there is some External code, but without errors or warnings, I suppose that I will receive what I passed. – Miroslav Endyš Jan 07 '19 at 11:13
  • @MiroslavEndyš can you try without the `IsAsync=true` in the Binding and see what happens? Also, can you check if your method `Drivers_ApplyFilter` is being called? – themightylc Jan 07 '19 at 11:27
  • @Clemens Please check last update. I found the way - probably. The issue is in retyping of object to the Driver. I need to found the way, how to keep ability of the binding value to be collection of generic object. – Miroslav Endyš Jan 07 '19 at 11:41
  • Probably on the Control side you should use IEnumerable interface instead of a generic interface IEnumerable. There is no implicit casting from IEnumerable to IEnumerable. However in the other direction it is ok. – Dmitry Jan 07 '19 at 11:59
  • @Dmitry Does not work. As I described below. I just need to find way... the same way as ListView is able passing SelectedItems via TwoWay binding. – Miroslav Endyš Jan 07 '19 at 12:16
  • I am able to get typeof binded property inside of control. So If I know, that SelectedItemsProperty is binded to the IEnumerable… can I try something like SetValue(SelectedItemsProperty, value.Cast()); But I don't know hot to Cast with Type saved in value.... – Miroslav Endyš Jan 07 '19 at 13:11

2 Answers2

0

Without going into your code too deeply, have you tried using SetCurrentValue instead of SetValue?

See this SO Answer

themightylc
  • 304
  • 2
  • 15
  • 1
    When the property is bound with Mode=TwoWay, calling SetValue does not replace the Binding, but just sets the source property of the Binding. Despite the many upvotes, that answer is wrong for the Text property of a TextBox, because it binds TwoWay by default. That said, the setter of the CLR wrapper of a dependency property must call SetValue, and nothing else. – Clemens Jan 07 '19 at 10:48
  • Does not work. Same result. SelectedItems has been set in ButtonColumnFilter - Count:4 SelectedFilterDrivers has been set in PlannerFilterAdapter - Count: – Miroslav Endyš Jan 07 '19 at 10:53
0

There is no implicit casting from IEnumerable<object> to IEnumerable<Driver>. You should set SelectedItems as IEnumerable in your Control.

By the way if finally you deside to implement your selected drivers list as an observable collection and you want to listen for changes in the control then on the control side you can try to cast to INotifyCollectionChanged that will provide you with events.

Dmitry
  • 2,033
  • 1
  • 22
  • 31
  • I tried to change it, still same result. I just need automatically fill the external (mean outside of control) property with selected items via bindings. The external properties will be always IEnumerable. Now, I know that problem is with retyping of object. So I have to find some way, how to get correct IEnumerable of sometype outside of control. But thanks for helping. – Miroslav Endyš Jan 07 '19 at 12:12