Search code examples
c#wpfmvvmobservablecollectionuielement

Custom UIElement does not update layout when an ObservableCollection changes through binding


Again a have a (probably) simple Problem.

I would like to create a custom UIElement (A Collection of Lines which stay orthogonal). This UIElement is used as View in my MVVM application.

Here is my code:

class RaOrthogonalLine : Canvas, INotifyPropertyChanged
    {
        public RaOrthogonalLine()
        {
            Points.CollectionChanged += Points_CollectionChanged;
        }

        void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Paint();
        }

        void Paint()
        {
             //PaintingStuff! Here I would like to get in!
        }

        void newLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (LineClicked != null)
                LineClicked(sender, e);
        }

        public delegate void LineClickedEventHandler(object sender, MouseButtonEventArgs e);
        public event LineClickedEventHandler LineClicked;

        public ObservableCollection<RaPoint> Points
        {
            get
            {
                return (ObservableCollection<RaPoint>)GetValue(PointsProperty);
            }
            set
            {
                SetValue(PointsProperty, value);
                RaisePropertyChanged("Points");
            }
        }
        public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(ObservableCollection<RaPoint>), typeof(RaOrthogonalLine),
            new FrameworkPropertyMetadata(new ObservableCollection<RaPoint>(), new PropertyChangedCallback(PointsPropertyChanged))
        {
            BindsTwoWayByDefault = true,
            DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
        }
        );

        private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RaOrthogonalLine thisLine = (RaOrthogonalLine)d;
            thisLine.Paint();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

    }

In my XAML I bind a ObservableCollection of my ViewModel to my ObservableCollection in the UIElement(View).

That works fine.

My problem now is that I do not get notified when the Collection changes (Add/Remove/..) - because then i would need to Repaint it.

I tried to get the Points.CollectionChanged event but it does not fire.

Has anyone a idea?

Thank you!


Solution

  • The problem is that you are adding the CollectionChanged Event handler in the constructor of your Control. In the constructor your Paint property is not binded to the right source yet (Indeed it has the PointsProperty's default value, i.e. an empty collection).

    You should add and remove the event handler in the PointsPropertyChanged method. Take a look to this sample code:

    public class RaOrthogonalLine : Canvas
    {
        public INotifyCollectionChanged Points
        {
            get { return (INotifyCollectionChanged)GetValue(PointsProperty); }
            set { SetValue(PointsProperty, value); }
        }
    
    
        public static readonly DependencyProperty PointsProperty =
            DependencyProperty.Register("Points", typeof(INotifyCollectionChanged), typeof(RaOrthogonalLine),
            new FrameworkPropertyMetadata(null, new PropertyChangedCallback(PointsPropertyChanged))
            {
                BindsTwoWayByDefault = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            });
    
        void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Paint();
        }
    
        private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RaOrthogonalLine raOrthogonalLine = (RaOrthogonalLine)d;
            INotifyCollectionChanged newValue = (INotifyCollectionChanged)e.NewValue;
            INotifyCollectionChanged oldValue = (INotifyCollectionChanged)e.OldValue;
    
            if (oldValue != null)
            {
                oldValue.CollectionChanged -= raOrthogonalLine.Points_CollectionChanged;
            }
    
            if (newValue != null)
            {
                newValue.CollectionChanged += raOrthogonalLine.Points_CollectionChanged;
            }
            raOrthogonalLine.Paint();
        }
    }
    

    I hope it can help you with your problem.