简体   繁体   中英

WPF Custom LED Checkbox

I currently trying to "port" some control from WindowsForms to WPF. I Have this stylish led checkbox and try to achieve the same visual appearance in wpf. but I'm unable to get it done.

I've searched a lot butt cannot find a solution to my questions/problems.

This is how the winforms Control looks like 在此处输入图片说明

The colored Circle size depends on the size of the control. The color is user definable. The color is used for the circle and the Text. It's bright if it ich checked and dimmed / gray when it's unchecked. The diark and highlight colors are calculated from the control color (lighter/darker).

All my tries to do the same in wpf pretty much failed up to now. :-( I fist tried to do it with an usercontrol, but decided it would be easier to have it derived from checkbox with just an extra option to set the color.

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
    xmlns:test="clr-namespace:LedTest"
    xmlns:uc="clr-namespace:WPFTest;assembly=LedControl"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    x:Class="LedTest.MainWindow"
    Title="MainWindow" Height="285" Width="566">
   <Window.Resources>
      <ResourceDictionary x:Key="ResDict2"  Source="Dictionary2.xaml"/>
   </Window.Resources>
   <Grid Margin="0">
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto" MinHeight="27" />
         <RowDefinition Height="75"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="10*" />
         <ColumnDefinition Width="179*"/>
      </Grid.ColumnDefinitions>
      <uc:LedControl x:Name="led1" 
            Color="ForestGreen" Text="Some Option"
         Grid.Column="1" Grid.Row="1" Height="39" VerticalAlignment="Bottom" Margin="0,0,0,36"/>
      <CheckBox Content="Some Option" Style="{DynamicResource TestStyle}" Margin="0,0,31,0" Grid.Column="1"/>
   </Grid>
</Window>

This is my LedControl code:

<UserControl x:Class="LedControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="100" d:DesignWidth="300">
   <UserControl.Resources>
   </UserControl.Resources>
   <StackPanel x:Name="gridBigLed" Orientation="Horizontal" >
      <Border x:Name="border1"
                BorderThickness="1" 
                Width="{Binding ActualHeight, ElementName=gridBigLed, Mode=OneWay}" 
                CornerRadius="{Binding ActualWidth, ElementName=gridBigLed, Mode=OneWay}"
                HorizontalAlignment="Left">
         <Border.Background>
            <RadialGradientBrush GradientOrigin="0.2,0.2">
               <GradientStop Color="#FFFFAAAA"/>
               <GradientStop x:Name="backgroundColor" Color="Red" Offset="1.2"/>
            </RadialGradientBrush>
         </Border.Background>
         <Border.BorderBrush>
            <RadialGradientBrush>
               <GradientStop x:Name="GradientColorLow" Color="#FF660000" Offset="0.383"/>
               <GradientStop x:Name="GradientColorHigh" Color="#330000" Offset="0.5"/>
            </RadialGradientBrush>
         </Border.BorderBrush>
      </Border>
      <Label Content="{Binding Text}" x:Name="LEDText" Foreground="Red"    HorizontalContentAlignment="Left" VerticalContentAlignment="Center"/>
   </StackPanel>
</UserControl>

and the code behind:

       public partial class LedControl : UserControl
   {
      #region Dependency properties
      /// <summary>Dependency property to Get/Set the current IsActive (True/False)</summary>
      public static readonly DependencyProperty IsCheckedProperty =
          DependencyProperty.Register("IsChecked", typeof(bool?), typeof(LedControl),
              new PropertyMetadata(null, new PropertyChangedCallback(LedControl.IsCheckedPropertyChanced)));

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

        public static readonly DependencyProperty TextProperty =
          DependencyProperty.Register("Text", typeof(string), typeof(LedControl),
              new PropertyMetadata("ButtonText", new PropertyChangedCallback(LedControl.OnTextPropertyChanged)));
      #endregion

      #region Properties
      /// <summary>Gets/Sets Text Value</summary>
      public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
      /// <summary>Gets/Sets Value</summary>
      public bool? IsChecked { get { return (bool?)GetValue(IsCheckedProperty); } set { SetValue(IsCheckedProperty, value); } }
      /// <summary>Gets/Sets Color</summary>
      public Color Color { get { return (Color)GetValue(ColorProperty); } set { SetValue(ColorProperty, value); } }
      #endregion

      #region Constructor
      public LedControl()
      {
         InitializeComponent();
         if (this.IsChecked == true)
         {
            this.LEDColor.Color = this.Color;
            this.LEDText.Foreground = new SolidColorBrush(this.Color);
         }
         else if (this.IsChecked == false)
         {
            this.LEDColor.Color = Colors.Gray;
            this.LEDText.Foreground = new SolidColorBrush(Colors.Gray);
         }
      }

      #endregion

  #region Callbacks

  private static void IsCheckedPropertyChanced(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
     LedControl led = (LedControl)d;

     if (led.IsChecked == true)
     {
        led.LEDColor.Color = led.Color;
        led.LEDText.Foreground = new SolidColorBrush(led.Color);
     }
     else
     {
        led.LEDColor.Color = Colors.Gray;   // TODO calculate dark/gray color
        led.LEDText.Foreground = new SolidColorBrush(Colors.Gray);
     }
  }

  private static void OnColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
     LedControl led = (LedControl)d;
     led.Color = (Color)e.NewValue;
     if (led.IsChecked == true)
     {   
        led.LEDColor.Color = led.Color;
        led.LEDText.Foreground = new SolidColorBrush( led.Color );
     }
  }

  private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
     LedControl led = (LedControl)d;
     led.Text = (String)e.NewValue;
  }

  #endregion

}

The thing is that the control does not work. I set the Color to forrestGreen, but shows up red in designer and if I execute the program:

在此处输入图片说明

The text "Some Option" is not shown as well..

I haven't figured out how to have the gradient colors to be darker and lighter versions of the color I want.

The look of the led is also not as cool as in winforms, but I have no clue to translate the code to wpf.

here is the part of the code that draws the led in win-Forms:

private void drawControl(Graphics g, bool on) {
     // Is the bulb on or off
     Color lightColor = (on) ? this.Color : Color.FromArgb(100, this.Color);
     Color darkColor = (on) ? this.DarkColor : Color.Gray/*this.DarkDarkColor*/;

     // Calculate the dimensions of the bulb
     int width = this.Width - (this.Padding.Left + this.Padding.Right);
     int height = this.Height - (this.Padding.Top + this.Padding.Bottom);
     // Diameter is the lesser of width and height
     int diameter = Math.Min(width, height);
     // Subtract 1 pixel so ellipse doesn't get cut off
     diameter = Math.Max(diameter - 1, 1);

     SolidBrush br = new SolidBrush(BackColor);
     g.FillRectangle(br, ClientRectangle);

     // Draw the background ellipse
     var rectangle = new Rectangle(this.Padding.Left, this.Padding.Top, diameter, diameter);
     g.FillEllipse(new SolidBrush(darkColor), rectangle);

     // Draw the glow gradient
     var path = new GraphicsPath();
     path.AddEllipse(rectangle);
     var pathBrush = new PathGradientBrush(path);
     pathBrush.CenterColor = lightColor;
     pathBrush.SurroundColors = new Color[] { Color.FromArgb(0, lightColor) };
     g.FillEllipse(pathBrush, rectangle);

     // Draw the white reflection gradient
     var offset = Convert.ToInt32(diameter * .15F);
     var diameter1 = Convert.ToInt32(rectangle.Width * .8F);
     var whiteRect = new Rectangle(rectangle.X - offset, rectangle.Y - offset, diameter1, diameter1);
     var path1 = new GraphicsPath();
     path1.AddEllipse(whiteRect);
     var pathBrush1 = new PathGradientBrush(path);
     pathBrush1.CenterColor = _reflectionColor;
     pathBrush1.SurroundColors = _surroundColor;
     g.FillEllipse(pathBrush1, whiteRect);

     // Draw the border
     g.SetClip(this.ClientRectangle);
     if (this.On) 
        g.DrawEllipse(new Pen(Color.FromArgb(85, Color.Black),1F), rectangle);

     if (this.Text != string.Empty)
     {
        RectangleF textArea = this.ClientRectangle;
        textArea.X += rectangle.Width + 6;
        textArea.Width -= (diameter + 6);
        Font fon = new Font(Font.FontFamily, Font.Size-1, FontStyle.Bold);

        StringFormat sf = new StringFormat();
        sf.Alignment = StringAlignment.Near;
        sf.LineAlignment = StringAlignment.Center;

        if (!this.On)
           g.DrawString(this.Text, fon, new SolidBrush(Color.Gray), textArea, sf);
        else
           g.DrawString(this.Text, fon, new SolidBrush(darkColor), textArea, sf);
     }

  }

My second try with the checkbox as base is nore or less useless, but perhaps someone is keen and can substitute the checkbox with the led.

Any help is appreciated!

here is a LedControl derived from CheckBox. LedControl itself adds OnColor and OffColor properties.

public class LedControl : CheckBox
{
    static LedControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LedControl), new FrameworkPropertyMetadata(typeof(LedControl)));
    }

    public static readonly DependencyProperty OnColorProperty =
        DependencyProperty.Register("OnColor", typeof(Brush), typeof(LedControl), new PropertyMetadata(Brushes.Green));

    public Brush OnColor
    {
        get { return (Brush)GetValue(OnColorProperty); }
        set { SetValue(OnColorProperty, value); }
    }

    public static readonly DependencyProperty OffColorProperty = 
        DependencyProperty.Register("OffColor", typeof(Brush), typeof(LedControl), new PropertyMetadata(Brushes.Red));

    public Brush OffColor
    {
        get { return (Brush)GetValue(OffColorProperty); }
        set { SetValue(OffColorProperty, value); }
    }
}

and visual appearance is customized via Style and Template. Main template parts are LedBorder ellipse, white CenterGlow ellipse, white CornerLight shape and of course ContentPresent. LedBorder adapts to LedControl height. Depending on IsChecked LedBorder is colored with OnColor or OffColor (as well as Foreground). Disabled control is grayed.

<Style TargetType="local:LedControl">
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="Margin" Value="5"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:LedControl">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>

                    <Grid Background="Transparent" Name="grd"
                            Margin="{TemplateBinding Padding}"
                            VerticalAlignment="Stretch" 
                            Width="{Binding Path=ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Self}}">

                        <Ellipse x:Name="LedBorder" 
                                    Fill="{TemplateBinding Background}"
                                    Stroke="{TemplateBinding BorderBrush}"
                                    StrokeThickness="2"
                                    Stretch="Uniform"/>

                        <Ellipse x:Name="CenterGlow" Stretch="Uniform">
                            <Ellipse.Fill>
                                <RadialGradientBrush>
                                    <GradientStop Color="White" Offset="-0.25"/>
                                    <GradientStop Color="Transparent" Offset="0.91"/>
                                </RadialGradientBrush>
                            </Ellipse.Fill>
                        </Ellipse>

                        <Ellipse x:Name="CornerLight" Stretch="Uniform" Margin="2">
                            <Ellipse.Fill>
                                <RadialGradientBrush Center="0.15 0.15" RadiusX="0.5" RadiusY="0.5">
                                    <GradientStop Color="White" Offset="0"/>
                                    <GradientStop Color="Transparent" Offset="1"/>
                                </RadialGradientBrush>
                            </Ellipse.Fill>
                        </Ellipse>
                    </Grid>

                    <ContentPresenter x:Name="content" Grid.Column="1" Margin="4,0,0,0"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            RecognizesAccessKey="True"/>

                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="true">
                        <Setter TargetName="LedBorder" Property="Fill" Value="{Binding Path=OnColor, RelativeSource={RelativeSource TemplatedParent}}"/>
                        <Setter TargetName="content" Property="TextElement.Foreground" Value="{Binding Path=OnColor, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>

                    <Trigger Property="IsChecked" Value="false">
                        <Setter TargetName="LedBorder" Property="Fill" Value="{Binding Path=OffColor, RelativeSource={RelativeSource TemplatedParent}}"/>
                        <Setter TargetName="content" Property="TextElement.Foreground" Value="{Binding Path=OffColor, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>

                    <Trigger Property="IsEnabled" Value="false">
                        <Setter TargetName="CenterGlow" Property="Fill">
                            <Setter.Value>
                                <RadialGradientBrush Opacity="1">
                                    <GradientStop Color="Transparent" Offset="-0.5" />
                                    <GradientStop Color="#888" Offset="1" />
                                </RadialGradientBrush>
                            </Setter.Value>
                        </Setter>
                        <Setter TargetName="content" Property="TextElement.Foreground" Value="#888"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and here is a sample:

<StackPanel>
    <local:LedControl Content="Disabled OFF" Height="24" IsChecked="False" IsEnabled="False" />
    <local:LedControl Content="Disabled ON" Height="32" IsChecked="True" IsEnabled="False" />
    <local:LedControl Content="Enabled OFF" OffColor="Chocolate" IsChecked="False" Height="40" />
    <local:LedControl Content="Enabled ON" OnColor="Navy" IsChecked="True" Height="48" />
</StackPanel>

在此处输入图片说明

You can use PathGradientBrush to draw a radial gradient. Here is the result of the code which I wrote. You can use any color as CheckedColor and UnCheckedColor , I used Red and Green to get this result:

在此处输入图片说明

Code

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class MyCheckBox : CheckBox
{
    public MyCheckBox()
    {
        this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        this.DoubleBuffered = true;
        this.ResizeRedraw = true;
        CheckedColor = Color.Green; ;
        UnCheckedColor = Color.Red; ;
    }
    [DefaultValue(typeof(Color), "Green")]
    public Color CheckedColor { get; set; }
    [DefaultValue(typeof(Color), "Red")]
    public Color UnCheckedColor { get; set; }
    protected override void OnPaint(PaintEventArgs e)
    {
        var darkColor = Color.Black;
        var lightColor = Color.FromArgb(200, Color.White);
        var cornerAlpha = 80;
        this.OnPaintBackground(e);
        using (var path = new GraphicsPath())
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            var rect = new Rectangle(0, 0, Height, Height);
            path.AddEllipse(rect);
            rect.Inflate(-1, -1);
            using (var bgBrush = new SolidBrush(darkColor))
            {
                e.Graphics.FillEllipse(bgBrush, rect);
            }
            using (var pathGrBrush = new PathGradientBrush(path))
            {
                var color = Checked ? CheckedColor : UnCheckedColor;
                pathGrBrush.CenterColor = color; ;
                Color[] colors = { Color.FromArgb(cornerAlpha, color) };
                pathGrBrush.SurroundColors = colors;
                e.Graphics.FillEllipse(pathGrBrush, rect);
            }
            using (var pathGrBrush = new PathGradientBrush(path))
            {
                pathGrBrush.CenterColor = lightColor; ;
                Color[] colors = { Color.Transparent };
                pathGrBrush.SurroundColors = colors;
                var r = (float)(Math.Sqrt(2) * Height / 2);
                var x = r / 8;
                e.Graphics.FillEllipse(pathGrBrush, new RectangleF(-x, -x, r, r));
                e.Graphics.ResetClip();
            }
        }
        TextRenderer.DrawText(e.Graphics, Text, Font,
                new Rectangle(Height, 0, Width - Height, Height), ForeColor,
                 TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM