HMI Controls for WPF: Led (and a tutorial on WPF User Control)

You can downlaod the source code with sample here: http://www.mesta-automation.com/Downloads/Led%20Wpf.rar

Every Scada system has his own framework that contains a lot of graphic objects, from gauges to charts, that can be controlled directly by some Tags or variables.
Unfortunately .Net doesn't come with this objects, so we have to create them.
In this sample i show how to create a simple graphical Led.

Create a new Control Library (File -> New -> Project -> WPF User Control Library),
then create a new project to test the control( to test and show how to use the control).
After the creation of this 2 projects, set the test project as startup project and add as reference the  control library.
Now we are ready to start creating the graphic part.

Basically a led can be drawn as a Border object that inside it contains a color, and his background is of another color.
This is the code for the graphic part
<Grid x:Name="gridBigLed" >
        <Border x:Name="border1" BorderThickness="6" Width="{Binding ActualHeight, ElementName=gridBigLed, Mode=OneWay}" CornerRadius="{Binding ActualWidth, ElementName=gridBigLed, Mode=OneWay}">
            <Border.Background>
                <RadialGradientBrush >
                    <GradientStop Color="White"/>
                    <GradientStop x:Name="backgroundColor" Color="Red" Offset="0.835"/>
                </RadialGradientBrush>
            </Border.Background>
            <Border.BorderBrush>
                <RadialGradientBrush>
                    <GradientStop Color="#FF020202" Offset="0.383"/>
                    <GradientStop Color="#FFE4E4E4" Offset="1"/>
                </RadialGradientBrush>
            </Border.BorderBrush>
        </Border>
    </Grid>


We bind the height of the border to the height of the grid, so we can resize the control when developing our application.
Corner radius is in binding with width too, to be sure that the border will be always circular.
For the background color you can use wathever you like. I used a radial gradient with Red(Off state) and White , and i will change it with green and  white (On state) depending on the state of a property. That's why I gave the name "backgroundColor" on the red GradientStop.

To give properties to a UserControl we must use Dependency Properties.
We will give 3 properties to the user control:
- bool IsActive
- Color ColorOn (color when IsActive is true)
- Color ColorOff (color when IsActive is false)

The declaration is the following:

/// <summary>Dependency property to Get/Set the current IsActive (True/False)</summary>
        public static readonly DependencyProperty IsActiveProperty =
            DependencyProperty.Register("IsActive", typeof(bool), typeof(Led),// null);
                new PropertyMetadata(new PropertyChangedCallback(Led.IsActivePropertyChanced)));

        /// <summary>Dependency property to Get/Set Color when IsActive is true</summary>
        public static readonly DependencyProperty ColorOnProperty =
            DependencyProperty.Register("ColorOn", typeof(Color), typeof(Led), //null);
                new PropertyMetadata(Colors.Green,new PropertyChangedCallback(Led.OnColorOnPropertyChanged)));

        /// <summary>Dependency property to Get/Set Color when IsActive is false</summary>
        public static readonly DependencyProperty ColorOffProperty =
            DependencyProperty.Register("ColorOff", typeof(Color), typeof(Led),// null);
                new PropertyMetadata(Colors.Red,new PropertyChangedCallback(Led.OnColorOffPropertyChanged)));

        /// <summary>Gets/Sets Value</summary>
        public bool IsActive
        {
            get { return (bool)GetValue(IsActiveProperty); }
            set 
            {
                SetValue(IsActiveProperty, value);
            }
        }

        /// <summary>Gets/Sets Color when led is True</summary>
        public Color ColorOn
        {
            get
            {
                return (Color)GetValue(ColorOnProperty);
            }
            set
            {
                SetValue(ColorOnProperty, value);
            }
        }

        /// <summary>Gets/Sets Color when led is False</summary>
        public Color ColorOff
        {
            get
            {
                return (Color)GetValue(ColorOffProperty);
            }
            set
            {
                SetValue(ColorOffProperty, value);
            }
        }

Now there is a lot to talk about dependency properties, but basically:
you can set the starting value when you create the objects (ColorOn starts with Color.Green and ColorOff start with Color.Red)
and you should note that every property has is name written as string in the 1st field of DependencyProperty.Register method.
the properties also access to values contained in dependency properties  by getValue() and SetValue().

Every Dependency Property should have is own method when it changes. Those methods describe the behaviour of our object, basically a change of IsActive should modify the color of the border and if i change a color at runtime, depending on the state, it should refresh the color instantly.
In this case i wrote the following methods:

private static void IsActivePropertyChanced(DependencyObject d, DependencyPropertyChangedEventArgs e) 
        {
            Led led = (Led)d;
            if ((bool)e.NewValue)
                led.backgroundColor.Color = led.ColorOn;
            else
                led.backgroundColor.Color = led.ColorOff;
        }

        private static void OnColorOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = (Led)d;
            led.ColorOn = (Color)e.NewValue;
            if (led.IsActive)
                led.backgroundColor.Color = led.ColorOn;
            else
                led.backgroundColor.Color = led.ColorOff;
        }

        private static void OnColorOffPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Led led = (Led)d;
            led.ColorOff = (Color)e.NewValue;
            if (led.IsActive)
                led.backgroundColor.Color = led.ColorOn;
            else
                led.backgroundColor.Color = led.ColorOff;
        }

Note that every method is static and it access to the user control property by casting the dependencyobject d.

As for the constructor, i wrote a refresh depending on what color it's set.

public Led()
{
   InitializeComponent();
   if (this.IsActive)
       this.backgroundColor.Color = ColorOn;
   else
       this.backgroundColor.Color = ColorOff;       
}


In this way our control should work.

Now to test we should try it with and without databinding.
This is the test that i wrote:

<Window x:Class="LedTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:uc="clr-namespace:LedControl;assembly=LedControl"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <uc:Led Name="led1" Height="60" Width="60" IsActive="False" Flashing="False" FlashingPeriod="500"  />
        <TextBlock Grid.Column="1">Led without binding</TextBlock>
        <Button Name="btnChange"  Grid.Column="2"  Content="Change" Click="btnChange_Click" />
        <uc:Led Name="led2" Grid.Row="2" Height="60" Width="60" IsActive="{Binding LedStatus}"  />
        <TextBlock  Grid.Row="2" Grid.Column="1">Led with binding</TextBlock>
        <Button Name="btnChange2"  Grid.Row="2" Grid.Column="2"  Content="Change" Click="btnChange2_Click" />
    </Grid>
</Window>
         


public partial class MainWindow : Window,INotifyPropertyChanged
    {
        bool ledStatus;
        public bool LedStatus
        { get { return ledStatus; } set { ledStatus = value; this.OnPropertyChanged("LedStatus"); } }

        public MainWindow()
        {
            InitializeComponent();
            led2.DataContext = this;
            led1.ColorOn = Colors.Blue;
            led1.ColorOff = Colors.Gray;
        }

        private void btnChange_Click(object sender, RoutedEventArgs e)
        {
            led1.IsActive = !led1.IsActive;
        }

        private void btnChange2_Click(object sender, RoutedEventArgs e)
        {
            LedStatus = !LedStatus;
        }


        #region INotifyPropertyChanged members

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }

        #endregion  

The 1st button test the led by changing the color from codebehind, the second test it in binding with a property contained (in this case) in the mainwindow directly.

Playing with Blend and adding more Dependency Properties will give even a better result.

Sample App is here: http://www.mesta-automation.com/Downloads/Led%20Wpf.rar

No comments: