Reputation: 1
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