在现代应用程序中,用户界面的直观性和响应性是至关重要的。为了增强用户体验,开发者经常需要创建具有动态视觉效果的自定义控件。本文将介绍如何在WPF应用程序中创建一个自定义的LED控制,它能够以动画形式显示不同组件的状态。这个LED控制不仅能够以代码形式动态创建,而且还可以配置,无需使用图像或额外资源。
首先,考虑了一个交通信号灯的模型,它默认显示三个LED(绿色、橙色、红色),分别代表组件的状态(正常、警告、错误)。目标是创建一个动画效果,使得只有一个LED在任何时候处于激活状态。为了实现这一点,设计了一个LED控制,它具备创建和动画功能。
为了遵循良好的编程实践,创建了两个程序集。一个模拟通用基础设施库,包含"LedControl"类;另一个程序集则在WPF应用程序中展示这个控件。虽然在实际项目中,通常会使用Prism框架来处理复合模式、依赖注入、MVVM等,但为了保持示例的简单性,在这个例子中直接在MainWindow的XAML代码中分配了ViewModel。
LED控制具有一些可配置的依赖属性:
LED的方向非常重要,因为它决定了当方向为水平时需要定义自定义控件的高度,而当方向为垂直时需要定义宽度。如果没有定义,自定义控件将使用可用的大小。
控件使用StackPanel,其中包含一系列Ellipse(LED)控件。Ellipse的画刷用于显示LED的颜色,并模拟开/关行为。LED的加载发生在控件加载后,这样就知道框架元素的大小了。加载事件在构造函数中注册:
public LedControl()
{
Loaded += LoadLeds;
}
控件的创建发生在LoadLeds方法中,该方法在控件加载后或属性Leds改变后被调用:
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();
}
定义了两个方法LedOn()和LedOff()来运行动画(在ActiveLed改变后),通过改变ellipses画刷的透明度。检查ActiveLed和当前Opacity是为了确保不会同时打开和关闭同一个LED。