Пользовательский элемент Digit предназначен для графической симуляции семисегментной цифры электронных дисплеев. Элемент Digit может использоваться в качестве индикатора текущий процессов и для повышения привлекательности пользовательских интерфейсов.
Графический элемент Digit для платформы WPF создан как класс производный от Panel. Наследование от класса Panel дает возможность элементу Digit способность размещения и упорядочения дочерних Polygon в разметке XAML и программном коде. Класс Digit написан на языке быстрой разработки C#.
Графическое векторное изображение семисегментной цифры создается в такой последовательности:
- из полигонов (System.Windows.Shapes.Polygon) создаются ромбовидные фигуры вплотную друг к другу для заготовки цифры;
- далее производится клипирование (System.Windows.Media.Geometry Clip), т. е. вырезание сегментов цифры из созданных ромбов. Для каждой фигуры определяется соответствующая прямоугольная геометрия отсечения;
- области, не входящие в прямоугольники, отсекаются и не рисуются системой. В результате образуются четкие сегменты электронной цифры.
Способ формирования требуемой конфигурации методом клипирования избавляет от необходимости сложного расчета точек многоугольников Polygon. Для реалистичности между сегментами цифры создаются промежутки путем смещения сегментов от центра пользовательского элемента. Созданные сегменты независимы друг от друга и позволяют определять свойства в отдельности для каждого сегмента, например: цвет, прозрачность, геометрию.
Пользовательский графический элемент Digit наследуя возможности Panel позволяет разрабатывать интерфейс в дизайнерском режиме. Для этого надо добавить элемент Digit в разметку XAML и далее атрибутами определить требуемые значения доступных свойств.
Кроме свойств самого пользовательского элемента Digit (например, цвет сегментов цифры) доступны для определений свойства родительского класса Panel (например, ширина, координаты размещения).
Класс Digit создает графическую композицию из элементов Shapes.Polygon для визуализации цифры подобной тем, что отображаются на электронных семисегментных индикаторах.
Элемент Digit имеет открытые свойства доступные при определении элемента в разметке XAML, упрощая разработку пользовательского интерфейса. Открытые свойства задают цвет фигур Polygon и, для повышения реалистичности, устанавливают зазоры между сегментами цифры.
Отношение ширины к высоте элемента Digit имеет фиксированное значение. Размер семисегментной цифры определяет только свойство Width родительского класса Panel, значение Height автоматически вычисляется после изменения ширины элемента.
Для правильного размещения и отображения фигур Polygon, пользовательский элемент Digit переопределяет родительские методы FrameworkElement.MeasureOverride(Size) и FrameworkElement.ArrangeOverride(Size) для, соответственно, измерения сегментов и их расстановки в пределах области элемента. После выполнения своих обязанностей родительскими методами окончательная корректировка размера элемента Digit производится методом Digit.ComputeSize().
Краткий листинг программного кода класса Digit
public class Digit : Panel
{
#region Поля и свойства
// Ширина цифры без фаски и промежутков.
double widthBlankSegment = 100;
// Высота квадрата верхней или нижней половинок цифры.
// Цифра состоит из верхней и нижней половинок.
public double heightBlankSegment = 100;
// Толщина сегментов - обрезанных (клипированных) полигонов.
double thicknessSegment = 30;
// Смещение точек линий смежных полигонов
// к центральному полигону (элементу).
// Для уравнивания толщины центрального сегмента
double correctCenter = 10;
// Смещение сегментов цифры друг от друга,
// значение изменяет промежуток между сегментами.
double _strokeThickness = 1;
public double StrokeThickness
{
get { return _strokeThickness; }
set { _strokeThickness = value; }
}
// Цвет сегментов цифры.
SolidColorBrush _colorDigit = Brushes.Black;
public SolidColorBrush ColorDigit
{
get { return _colorDigit; }
set
{
_colorDigit = value;
// Если в XAML был сброс или в других случаях,
// то восстанавливаем кисть по умолчанию.
if (value == null) _colorDigit = Brushes.Black;
// Перерисовываем сегменты.
DrawDigit(0, 0);
}
}
#endregion
#region Инициализация
public Digit() : base()
{
// Размер цифры по умолчанию
Width = 100;
// Инициализация полигонов - сегментов цифры,
// и добавления их в коллекцию дочерних сегментов
// родительской панели.
for (int i = 0; i < 7; i++)
{
Children.Add(new Polygon());
}
}
#endregion
#region Управление значением цифры
// Визуализация значений цифры.
public void ValueDigit(int digit)
{
switch (digit)
{
case 0:
// Left
Children[0].Opacity = 1;
// Top
Children[1].Opacity = 1;
// Right
Children[2].Opacity = 1;
// Center
Children[3].Opacity = 0;
// LeftBottom
Children[4].Opacity = 1;
// Bottom
Children[5].Opacity = 1;
// RightBottom
Children[6].Opacity = 1;
break;
case 1:
// Left
Children[0].Opacity = 0;
// Top
Children[1].Opacity = 0;
// Right
Children[2].Opacity = 1;
// Center
Children[3].Opacity = 0;
// LeftBottom
Children[4].Opacity = 0;
// Bottom
Children[5].Opacity = 0;
// RightBottom
Children[6].Opacity = 1;
break;
case 2:
...
break;
case 3:
...
break;
case 4:
...
break;
case 5:
...
break;
case 6:
...
break;
case 7:
...
break;
case 8:
...
break;
case 9:
...
break;
}
}
#endregion
#region Вычисление размеров для сегментов цифры
private void ComputeSize()
{
// Вычисление ширины цифры на основе заданной пользователем общей ширины.
widthBlankSegment = Width - (2 * thicknessSegment / 3 +
_strokeThickness * 2);
// Высота сегментов половинки и ширина цифры всегда одинаковые.
// Цифра состоит из верхней половинки и нижней половинки.
heightBlankSegment = widthBlankSegment;
// Корректировка толщины центрального сегмента,
// для уравнивания с толщинами других сегментов.
correctCenter = widthBlankSegment / 8;
// Корректировка толщины сегментов цифры.
thicknessSegment = widthBlankSegment / 4;
// Вычисление общей высоты цифры с верхней и нижней половинками.
Height = heightBlankSegment * 2 + 2 * thicknessSegment / 3 +
_strokeThickness * 2 + _strokeThickness * 2;
// Рисование полной цифры с новыми размерами.
DrawDigit(0, 0);
}
#endregion
#region Рисование графики элементов цифры
void DrawDigit(double x, double y)
{
x = x +
// Смещение вправо на фаску левых сегментов
thicknessSegment / 3 +
// Смещение вправо на толщину промежутка
// (смещение левых сегментов вправо для создания промежутка).
_strokeThickness;
// Смещение по высоте на фаску сегмента и
// межсегментного промежутка
// (смещения верхнего элемента вверх для создания промежутка между сегментами).
y = y + thicknessSegment / 3 + _strokeThickness;
// Рисование сегментов цифры.
SegmentLeft(x, y, _colorDigit, _strokeThickness);
SegmentTop(x, y, _colorDigit, _strokeThickness);
SegmentRight(x, y, _colorDigit, _strokeThickness);
SegmentCenter(x, y, _colorDigit, _strokeThickness);
SegmentLeftBottom(x, y, _colorDigit, _strokeThickness);
SegmentBottom(x, y, _colorDigit, _strokeThickness);
SegmentRightBottom(x, y, _colorDigit, _strokeThickness);
}
void SegmentLeft(double x, double y, SolidColorBrush color, double strokeThickness)
{
Polygon pg = (Polygon)Children[0];
PointCollection Points = new()
{
// left
new System.Windows.Point(x - widthBlankSegment / 2, y +
heightBlankSegment / 2),
// top
new Point(x, y),
// right
new Point(x + widthBlankSegment / 2, y + heightBlankSegment / 2),
// right2
new Point(x + widthBlankSegment / 2, y + heightBlankSegment / 2 +
correctCenter /*корректирвка центрального*/),
// bottom
new Point(x, y + heightBlankSegment)
};
pg.Points = Points;
pg.Fill = color;
TranslateTransform tt = new()
{
X = -strokeThickness
};
pg.RenderTransform = tt;
// Обрезание прямоугольного полигона до трапеции.
RectangleGeometry rg = new()
{
Rect = new Rect(x - thicknessSegment / 3, y,
thicknessSegment, heightBlankSegment)
};
pg.Clip = rg;
}
void SegmentTop(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
void SegmentRight(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
void SegmentCenter(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
void SegmentLeftBottom(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
void SegmentBottom(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
void SegmentRightBottom(double x, double y, SolidColorBrush color, double strokeThickness)
{
...
}
#endregion
#region Переопределенные методы класса Panel
// Измерение дочерних элементов
protected override Size MeasureOverride(Size availableSize)
{
Size panelDesiredSize = new();
foreach (UIElement child in InternalChildren)
{
// Метод измерения Measure должен вызываться для
// каждого дочернего элемента Panel,
// в противном случае дочерние элементы
// не будут иметь правильного размера или упорядочения.
child.Measure(availableSize);
panelDesiredSize = child.DesiredSize;
}
return panelDesiredSize;
}
// Размещение дочерних элементов
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
// Родительский класс Panel будет вызывать Arrange(Rect)
// для каждого дочернего элемента,
// в противном случае дочерние элементы не будут отображаться правильно.
child.Arrange(new Rect(new Point(), child.DesiredSize));
}
// Вычисление размеров всей цифры в зависимости от ширины панели.
ComputeSize();
return finalSize;
}
#endregion
}
Пример применения класса электронных цифр Digit показан как приложение двухразрядного счетчика. Программа демонстрации работы счетчика прикреплена к архиву исходника. Анимацию работы программы можно посмотреть выше.
Программный код управления приложением двухразрядного счетчика на электронных цифрах:
// Таймер счетчика
readonly DispatcherTimer dispatcherTimer;
public MainWindow()
{
InitializeComponent();
// Инициализация и запуск таймера.
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += DispatcherTimer_Tick; ;
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);
}
int count = 0;
private void DispatcherTimer_Tick(object? sender, EventArgs e)
{
// Разряд - десятки
int pos1 = count / 10;
// Разряд - единицы
int pos2 = count % 10;
digit1.ValueDigit(pos1);
digit2.ValueDigit(pos2);
// Периодические изменения цвета цифр.
if (pos1 % 4 == 0) digit1.ColorDigit = digit2.ColorDigit = Brushes.Red;
else if (pos1 % 3 == 0) digit1.ColorDigit = digit2.ColorDigit = Brushes.Green;
else if (pos1 % 2 == 0) digit1.ColorDigit = digit2.ColorDigit = Brushes.Black;
else digit1.ColorDigit = digit2.ColorDigit = Brushes.Blue;
// Считаем до 100
// и сброс в начало отсчета.
count++;
if (count == 100) count = 0;
}
// Обработчик события нажатия на кнопки управления.
private void Button_Click(object sender, RoutedEventArgs e)
{
if(sender is Button button)
{
string? text = button.Content.ToString();
switch (text)
{
case "Start":
if (dispatcherTimer.IsEnabled == false) dispatcherTimer.Start();
else dispatcherTimer.Stop();
break;
case "Zoom":
if (digit1.Width == 80) digit1.Width = digit2.Width = 200;
else digit1.Width = digit2.Width = 80;
break;
}
}
}
Исходник семисегментной цифры написан на языке C# .NET6 в среде MS Visual Studio 2022. В состав файла архива входит файлы проекта и скомпилированный файл программы демонстрирования графического счетчика на электронных цифрах.