Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / WPF

WPF LED Custom Control in Code Behind

Rate me:
Please Sign up or sign in to vote.
3.69/5 (11 votes)
24 Aug 2018CPOL3 min read 18.2K   7   13   3
LED (for example, traffic light) control in code behind. Custom colors, opacity and sizeable.

 

Multiple LED controls

Introduction

We needed a control for a WPF windows infrastructure library that visually showed us the state of different components within our application. The most obvious thing that popped into my mind was a traffic signal that, per default, showed me three LEDs (green, orange, red) that indicate the components state (Ok, Warning, Error) of which only one LED could be active.

For pleasing the end user, we wanted to make them animated. For my own sake, I wanted to make them animated, configurable, not using images and not using resources. Therefore, I created an LED control with control creation and animation functionality in code behind.

Using the Code

For the sake of good practice, I have created two assemblies. One that simulates the common infrastructure library that contains the "LedControl" and one assembly that shows the control within a WPF application. I usually use Prism for composite pattern, injections, MVVM, etc. but to keep this example simple, I have created two classes in the MainWindow assembly in the MVVM folder and assigned the ViewModel directly within the MainWindow XAML code. Not a good practice but I wanted to keep the sample simple. The LED control was created with a certain contract in mind and was therefore only tested within that contract.

Controls Properties

The control has the following configurable dependency properties:

List<Color> Leds

A list of colors that is used to display LEDs in those defined colors. The default is green, orange and red.
Images below show the default LEDs in their on/off mode.

Green Orange Red
Green Green Red

double OffOpacity

The opacity of the LED color in off mode. The default is 0.4.

int ActiveLed

Active index of the LED. E.g. 0=nothing active, 1=first LED within the LEDs list is active, 2=second, etc.

Orientation LedOrientation

The orientation of the LEDs. Horizontal or Vertical. The default is horizontal.

The importance of the orientation is that one should define a height on the custom control when the orientation is horizontal and a width if the orientation is vertical. This gets used to control the size of the LEDs.
If not, set the custom control will use the size available.

The control uses a StackPanel that contains a list of Ellipse (LED) controls. The brush of the ellipse is used to display the color of the LED and to simulate the on/off behavior.

The loading of the LEDs happens after the control is loaded so that we know the sizes of the framework elements. The loaded event gets registered in the constructor:

C#
public LedControl()
{
    Loaded += LoadLeds;
}

The control creation happens in the LoadLeds method that is called either after the control is loaded or the property Leds has changed.

C#
private void LoadLeds(object sender, RoutedEventArgs e)
{
    FrameworkElement parent = Parent as FrameworkElement;
    StackPanel panel = new StackPanel();
    Content = panel;
    panel.Orientation = LedOrientation;
    panel.Children.Clear();
    ellipses.Clear();
    double size;

    if (LedOrientation == Orientation.Horizontal)
    {
        size = Height;
    }
    else
    {
        size = Width;
    }
    // Give it some size if forgotten to define width or height in combination with orientation
    if ((size.Equals(double.NaN)) && (parent != null) && (Leds.Count != 0))
    {
        if (parent.ActualWidth != double.NaN)
        {
            size = parent.ActualWidth / Leds.Count;
        }
        else if (parent.ActualHeight != double.NaN)
        {
            size = parent.ActualHeight / Leds.Count;
        }
    }
    // Create LED for each defined color in Leds
    foreach (Color color in Leds)
    {
        Ellipse ellipse = new Ellipse();
        ellipse.Height = size > 4 ? size - 4 : size;
        ellipse.Width = size > 4 ? size - 4 : size;
        ellipse.Margin = new Thickness(2);
        ellipse.Style = null;
        // Border for led
        RadialGradientBrush srgb = new RadialGradientBrush(new GradientStopCollection
        {
            new GradientStop(Color.FromArgb(255, 211, 211, 211), 0.8d),
            new GradientStop(Color.FromArgb(255, 169, 169, 169), 0.9d),
            new GradientStop(Color.FromArgb(255, 150, 150, 150), 0.95d),
        });

        if (size <= 50)
        {
            ellipse.StrokeThickness = 5;
        }
        else if (size <= 100)
        {
            ellipse.StrokeThickness = 10;
        }
        else
        {
            ellipse.StrokeThickness = 20;
        }

        srgb.GradientOrigin = new System.Windows.Point(0.5d, 0.5d);
        srgb.Center = new System.Windows.Point(0.5d, 0.5d);
        srgb.RadiusX = 0.5d;
        srgb.RadiusY = 0.5d;
        ellipse.Stroke = srgb;
        // Color of led
        RadialGradientBrush rgb = new RadialGradientBrush(new GradientStopCollection
        {
            new GradientStop(Color.FromArgb(150, color.R, color.G, color.B), 0.1d),
            new GradientStop(Color.FromArgb(200, color.R, color.G, color.B), 0.4d),
            new GradientStop(Color.FromArgb(255, color.R, color.G, color.B), 1.0d),
        });

        rgb.GradientOrigin = new System.Windows.Point(0.5d, 0.5d);
        rgb.Center = new System.Windows.Point(0.5d, 0.5d);
        rgb.RadiusX = 0.5d;
        rgb.RadiusY = 0.5d;
        // ellipse.Fill is used as animation target
        ellipse.Fill = rgb;
        ellipse.Fill.Opacity = OffOpacity;
        panel.Children.Add(ellipse);
        ellipses.Add(ellipse);
    }

    LedOn();
}

The two methods LedOn():

C#
private void LedOn()
{
    DoubleAnimation animation = new DoubleAnimation();
    animation.From = OffOpacity;
    animation.To = 1.0d;
    animation.Duration = new Duration(TimeSpan.FromSeconds(1));
    animation.AutoReverse = false;

    for (int i = 0; i < ellipses.Count; i++)
    {
        if ((ActiveLed - 1 == i) && (ellipses[i].Fill.Opacity < 1.0))
        {
            ellipses[i].Fill.BeginAnimation(Brush.OpacityProperty, animation);
        }
    }
}

and LedOff():

C#
private void LedOff()
{
    DoubleAnimation animation = new DoubleAnimation();
    animation.From = 1.0d;
    animation.To = OffOpacity;
    animation.Duration = new Duration(TimeSpan.FromSeconds(1));
    animation.AutoReverse = false;

    for (int i = 0; i < ellipses.Count; i++)
    {
        if ((ActiveLed - 1 != i) && (ellipses[i].Fill.Opacity > OffOpacity))
        {
            ellipses[i].Fill.BeginAnimation(Brush.OpacityProperty, animation);
        }
    }
}

Run an animation (after ActiveLed has changed) by changing the opacity, on the ellipses brush. The check for the ActiveLed and the current Opacity is done so we don’t switch on and off the same LED at the same time.

How to Use the Control

In the MainWindowxaml, you can find some examples of how to use the control and to test your own creations. Some are as follows:

XML
<controls:LedControl ActiveLed="{Binding ActiveLedItem1}"
LedOrientation="Vertical" Width="50" />

<controls:LedControl ActiveLed="{Binding ActiveLedItem1}"
Height="125" />

<controls:LedControl ActiveLed="{Binding ActiveLedItem1}"
LedOrientation="Vertical" Width="50" Leds="{Binding Colors}" />

History

  • 22nd August, 2018: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) KALINK GmbH
United States United States
Pascal has been a passionate software engineer for over 20 years. A native of Switzerland and United States Of America Permanent Resident, he has worked around the world, from Ireland, England and Germany to as far afield as USA, Australia and China.

Having started in C/C++ and moved on to C# on .NET he now completes the SDLC by also defining architectures, designs, requirement analyses, project management and definition of agile issue tracking items like Epics, Features, Stories, Tasks, Change Requests. He is versatile and also implements Ops-Servers, CI/CD pipelines, code analysis and vulnerability scans. Pascal always does his best to keep up with new technologies, processes and methodologies.

He is interested in networking with like-minded engineers around the world.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Hyland Computer Systems24-Aug-18 6:43
Hyland Computer Systems24-Aug-18 6:43 
QuestionDownload demo/source - 15.7 KB Pin
FredAu24-Aug-18 0:53
FredAu24-Aug-18 0:53 
AnswerRe: Download demo/source - 15.7 KB Pin
pkaelin24-Aug-18 20:13
pkaelin24-Aug-18 20:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.