Elia Suardi
Elia Suardi

Reputation: 1

Updating DataContext of UserControl in ItemsControl at runtime doesn't work

I am trying to update the state of LED UserControls after loading them into an ItemsControl, but I notice that an initial PropertyChanged event is triggered on the UserControl property while successive value changes do not trigger the event.

Here are the classes that I've implemented.

MainWindow XAML

<Window x:Class="Playground.MainWindow"
        xmlns:local="clr-namespace:Playground"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <Button Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding UpdateStateCommand}" Margin="20">CHANGE STATE</Button>

        <ItemsControl Grid.Row="1" ItemsSource="{Binding Leds}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <local:Led Diameter="30" OnStateBrush="{Binding OnBrush}" OffStateBrush="{Binding OffBrush}" State="{Binding LedState}" Margin="10"/>
                        <TextBlock Text="{Binding LedState}" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

MainWindow Code Behind

using System.Windows;

namespace Playground
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            DataContext = new MainViewModel_Led();
            InitializeComponent();

        }
    }
}

MainViewModel_Led class

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using System.Windows.Media;

namespace Playground
{
    public class MainViewModel_Led : INotifyPropertyChanged
    {

        #region Binding Properties

        private ObservableCollection<LedModel> ledColl;
        public ObservableCollection<LedModel> Leds
        {
            get { return ledColl; }
            private set
            {
                ledColl = value;
            }
        }

        #endregion

        #region Constructor

        public MainViewModel_Led()
        {
            generateLeds();
        }

        private void generateLeds()
        {
            Leds = new ObservableCollection<LedModel>();
            ObservableCollection<LedModel> coll = new ObservableCollection<LedModel>();

            for (int i = 0; i < 1; i++)
            {
                coll.Add(new LedModel()
                {
                    OnBrush = new SolidColorBrush(Colors.Orange),
                    OffBrush = new SolidColorBrush(Colors.Purple),
                    LedState = Led.LedStateType.ON
                });
            }

            Leds = coll;
        }

        #endregion

        #region Commands

        private ICommand _updateStateCommand;
        public ICommand UpdateStateCommand
        {
            get
            {
                return _updateStateCommand ?? (_updateStateCommand = new CommandHandler(() => MyAction(), () => true));
            }
        }

        private void MyAction()
        {
            if (Leds[0].LedState == Led.LedStateType.ON)
            {
                Leds[0].LedState = Led.LedStateType.OFF;
            }
            else if (Leds[0].LedState == Led.LedStateType.OFF)
            {
                Leds[0].LedState = Led.LedStateType.BLINKING;
            }
            else if (Leds[0].LedState == Led.LedStateType.BLINKING)
            {
                Leds[0].LedState = Led.LedStateType.ON;
            }
        }

        #endregion

        #region INotifyPropertyChanged implementation

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

Led UserControl XAML

<UserControl x:Class="Playground.Led"
             xmlns:local="clr-namespace:Playground"
             x:Name="uc_Led">
    <StackPanel Orientation="Vertical">
        <Ellipse x:Name="ellipse" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
             Stroke="Black" StrokeThickness="3"
             Width="{Binding Diameter, ElementName=uc_Led}"
             Height="{Binding Diameter, ElementName=uc_Led}"/>
        <TextBlock Text="{Binding State, ElementName=uc_Led}" />
        <TextBlock Text="{Binding OnStateBrush, ElementName=uc_Led}" />
        <TextBlock Text="{Binding OffStateBrush, ElementName=uc_Led}" />
    </StackPanel>
</UserControl>

Led UserControl Code Behind

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

namespace Playground
{
    /// <summary>
    /// Interaction logic for Led.xaml
    /// </summary>
    public partial class Led : UserControl
    {
        #region Variables

        // timer for blinking state
        private DispatcherTimer _timer;

        // LedState enum
        public enum LedStateType { ON, OFF, BLINKING };

        #endregion

        #region Constructor

        public Led()
        {
            InitializeComponent();

            // timer for blinking state
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromSeconds(0.5);
            _timer.Tick += _dt_Tick;

            this.Loaded += Led_Loaded;
        }

        #endregion

        #region Dependency Properties

        // brush for ON state
        private static readonly DependencyProperty onStateBrushProperty =
            DependencyProperty.Register("OnStateBrush", typeof(Brush), typeof(Led), new PropertyMetadata(new SolidColorBrush(Colors.Red), OnStateBrushPropertyChanged));

        // brush for OFF state
        private static readonly DependencyProperty offStateBrushProperty =
            DependencyProperty.Register("OffStateBrush", typeof(Brush), typeof(Led), new PropertyMetadata(new SolidColorBrush(Colors.DimGray), OffStateBrushPropertyChanged));

        // led state (ON OFF BLINKING)
        private static readonly DependencyProperty stateProperty =
            DependencyProperty.Register("State", typeof(LedStateType), typeof(Led), new PropertyMetadata(LedStateType.OFF, StatePropertyChanged));

        // led diameter
        private static readonly DependencyProperty diameterProperty =
            DependencyProperty.Register("Diameter", typeof(double), typeof(Led), new PropertyMetadata(18.0, DiameterPropertyChanged));

        #endregion

        #region Property Changed Callbacks

        private static void OnStateBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = d as Led;
            led.OnStateBrushPropertyChanged(e);
        }

        private void OnStateBrushPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            OnStateBrush = (Brush)e.NewValue;
        }

        private static void OffStateBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = d as Led;
            led.OffStateBrushPropertyChanged(e);
        }
        private void OffStateBrushPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            OffStateBrush = (Brush)e.NewValue;
        }

        private static void StatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = d as Led;
            led.StatePropertyChanged(e);
        }
        private void StatePropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            State = (LedStateType)e.NewValue;
        }

        private static void DiameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = d as Led;
            led.DiameterPropertyChanged(e);
        }
        private void DiameterPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            Diameter = (double)e.NewValue;
        }


        #endregion

        #region Properties

        /*
         * OnStateBrush property
         */
        public Brush OnStateBrush
        {
            get { return (Brush)GetValue(onStateBrushProperty); }
            set
            {
                SetValue(onStateBrushProperty, value);
            }
        }

        /*
         * OffStateBrush property
         */
        public Brush OffStateBrush
        {
            get { return (Brush)GetValue(offStateBrushProperty); }
            set
            {
                SetValue(offStateBrushProperty, value);
            }
        }

        /*
         * State property
         */
        public LedStateType State
        {
            get { return (LedStateType)GetValue(stateProperty); }
            set
            {
                SetValue(stateProperty, value);
                _updateEllipseBrush(value);
            }
        }

        /*
         * Diameter property
         */
        public double Diameter
        {
            get { return (double)GetValue(diameterProperty); }
            set
            {
                SetValue(diameterProperty, value);
            }
        }

        #endregion

        #region Public Methods

        #endregion

        #region Private Methods

        /// <summary>
        /// invoked when control is loaded
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Led_Loaded(object sender, RoutedEventArgs e)
        {
            _updateEllipseBrush(State);
        }

        /// <summary>
        /// updates ellipse fill color according to led state
        /// </summary>
        /// <param name="state">led state</param>
        private void _updateEllipseBrush(LedStateType state)
        {
            if (state == LedStateType.BLINKING)
            {
                // set initial fill and enable timer
                ellipse.Fill = OnStateBrush;
                _timer.Start();
            }
            else
            {
                // disable timer if still enabled
                if (_timer != null)
                    _timer.Stop();

                if (state == LedStateType.ON)
                {
                    ellipse.Fill = OnStateBrush;
                }
                else if (state == LedStateType.OFF)
                {
                    ellipse.Fill = OffStateBrush;
                }
            }
        }

        /// <summary>
        /// invoked on timer tick
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _dt_Tick(object sender, EventArgs e)
        {
            Brush ellipseBrush = ellipse.Fill;

            if (ellipseBrush == OnStateBrush)
            {
                ellipse.Fill = OffStateBrush;
            }
            else if (ellipseBrush == OffStateBrush)
            {
                ellipse.Fill = OnStateBrush;
            }
        }

        #endregion
    }
}

LedModel class

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Media;

namespace Playground
{
    public class LedModel : INotifyPropertyChanged
    {
        private Brush _onBrush;
        private Brush _offBrush;
        private Led.LedStateType _ledState;
        public Brush OnBrush
        {
            get { return _onBrush; }
            set
            {
                _onBrush = value;
                this.OnPropertyChanged();
            }
        }
        public Brush OffBrush
        {
            get { return _offBrush; }
            set
            {
                _offBrush = value;
                this.OnPropertyChanged();
            }
        }
        public Led.LedStateType LedState
        {
            get { return _ledState; }
            set
            {
                _ledState = value;
                this.OnPropertyChanged();
            }
        }

        #region INotifyPropertyChanged implementation

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

I expect that the Led UserControl state switches between the states ON/OFF/BLINKING when I click the top button.

What am I missing? Thanks in advance.

Upvotes: 0

Views: 12

Answers (0)

Related Questions