Предисловие
Некоторое время назад я писал статьи о разработке компонентов на WinForms, о разработке на WPF в стиле WinForms, а теперь хочу поделиться опытом разработки компонентов на WPF, но в “стиле WPF”.
Кому интересно, прошу под кат.
Постановка задачи
Задача такая же как и прежде разработать компонент для отображения слова ДПК. Слово ДПК 32-х разрядное, состоит из адреса – 8 бит и данных – 24 бита. На рисунке показано как выглядит результат.
Выбранное решение
Для решения этой задачи я выбрал “Пользовательский настраиваемый компонент” – по терминологии Visual Studio.
Создал решение с двумя проектами:
- Debug_WpfApplication – запускаемый проект для отладки;
- WpfCustomControlLibrary – собственно проект с компонентом.
На рисунке представлено как это выглядит в Visual Studio.
Разбор решения
Как видно, из первого рисунка компонент слова ДПК (CustomDpkView) состоит из 4-х областей:
- «TextBlock» Адрес;
- Значение адреса с 1 по 8 бит (Разработанный компонент CustomBinView);
- «TextBlock» Данные;
- Значение данных с 9 по 32 бит (Разработанный компонент CustomBinView);
Итак, что же из себя представляют эти два компонента CustomBinView и CustomDpkView?
Поля и методы (данные и поведение) описаны в соответствующих «*.cs» файлах и графический макет (стиль) описан в файле «Generic.xaml».
Компонент CustomBinView
Рассмотрим «CustomBinView.cs».
Здесь описаны DependecyPropery и их регистрация.
//Значение по-умолчанию кол-ва ячеек const int DEFAULT_CNT=8; static CustomBinView() { //Переопределение стандартного стиля DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomBinView), new FrameworkPropertyMetadata(typeof(CustomBinView))); ArrayList temp = new ArrayList(DEFAULT_CNT); for (int i = 0; i < DEFAULT_CNT; i++) temp.Add(true); ValuesProperty = DependencyProperty.Register("Values", typeof(ArrayList), typeof(CustomBinView), new FrameworkPropertyMetadata(temp, new PropertyChangedCallback(InvalidateVisualCallback))); FirstNumberProperty = DependencyProperty.Register("FirstNumber", typeof(int), typeof(CustomBinView), new FrameworkPropertyMetadata(new PropertyChangedCallback(InvalidateVisualCallback))); ColorTrueProperty = DependencyProperty.Register("ColorTrue", typeof(Brush), typeof(CustomBinView), new FrameworkPropertyMetadata(new PropertyChangedCallback(InvalidateVisualCallback))); ColorFalseProperty = DependencyProperty.Register("ColorFalse", typeof(Brush), typeof(CustomBinView), new FrameworkPropertyMetadata(new PropertyChangedCallback(InvalidateVisualCallback))); IsVisibleTextProperty = DependencyProperty.Register("IsVisibleText", typeof(bool), typeof(CustomBinView), new FrameworkPropertyMetadata(new PropertyChangedCallback(InvalidateVisualCallback))); } //При изменении свойств, влияющих на визуальный вид - перерисовывать компонент private static void InvalidateVisualCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CustomBinView)sender).InvalidateVisual(); } //------------------------------------------------------------------------------------------// #region DependencyProperty и их регистрация public static DependencyProperty ValuesProperty; public static DependencyProperty FirstNumberProperty; public static DependencyProperty ColorTrueProperty; public static DependencyProperty ColorFalseProperty; public static DependencyProperty IsVisibleTextProperty; #endregion //------------------------------------------------------------------------------------------// #region Свойства доступа к значениям DependencyProperty //Ячейки со значениями public ArrayList Values { get { return (ArrayList)GetValue(ValuesProperty); } set { SetValue(ValuesProperty, value); } } //Номер, с которого будут считаться ячейки public int FirstNumber { get { return (int)GetValue(FirstNumberProperty); } set { SetValue(FirstNumberProperty, value); } } //Цвет true-значения public Brush ColorTrue { get { return (Brush)GetValue(ColorTrueProperty); } set { SetValue(ColorTrueProperty, value); } } //цвет false-значения public Brush ColorFalse { get { return (Brush)GetValue(ColorFalseProperty); } set { SetValue(ColorFalseProperty, value); } } //Признак отображения текста в ячейках public bool IsVisibleText { get { return (bool)GetValue(IsVisibleTextProperty); } set { SetValue(IsVisibleTextProperty, value); } } #endregion
Ниже описана реализация рендеринга и алгоритма клика по ячейкам.
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {//при изменении размеров перерисовывать содержимое base.OnRenderSizeChanged(sizeInfo); InvalidateVisual(); } protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); DrawingContext dc = drawingContext; //Отрисовка фона и гриниц dc.DrawRectangle(Background, null, new Rect(new Point(0, 0), RenderSize)); #region Расчёт размеров и проверка условий if ((Values == null) || (Values.Count == 0)) return; double FullWidthCell = RenderSize.Width / (double)(Values.Count); double WidthCell = FullWidthCell; double HeightCell = RenderSize.Height; if ((WidthCell > 2) && (HeightCell > 2)) { WidthCell -= 2; HeightCell -= 2; } #endregion #region Отрисовка ячеек //Текущий номер ячейки, который будет рисоваться в ячейке int currentNumber = FirstNumber; //точка-указатель на текущую рисующуюся ячейку Point currentPointCell = new Point(1, 1); //Размер длины окургления ячейки double roundedLenght = ((0.15 * WidthCell) > (0.15 * HeightCell)) ? (0.15 * HeightCell) : (0.15 * WidthCell); foreach (bool item in Values)//отрисовка всех ячеек { //отрисовка прямоугольника ячейки с фоном dc.DrawRoundedRectangle((item) ? ColorTrue : ColorFalse, new Pen(BorderBrush, 1), new Rect(currentPointCell, new Size(WidthCell, HeightCell)), roundedLenght, roundedLenght); //отрисовка текста ячейки if (IsVisibleText) Service.DrawTxt(dc, currentNumber.ToString().PadLeft(2, '0'), currentPointCell, new Size(WidthCell, HeightCell), Foreground, this.FontFamily, this.FontStyle, this.FontWeight); //переход к следующей ячейке currentPointCell.X += FullWidthCell; currentNumber++; } #endregion } #region Маршрутизируемое событие "Клик по элементу" //Класс с аргументами для события public class ClickItemRoutedEventArgs : RoutedEventArgs { //Индекс элемента (нумерация с 0) public int Index { get; protected set; } //Сам элемент public object Item { get; protected set; } //Информация о клике мыши public MouseButtonEventArgs MouseEventArg { get; protected set; } public ClickItemRoutedEventArgs(RoutedEvent routedEvent, object item, int index, MouseButtonEventArgs arg) : base(routedEvent) { Item = item; Index = index; MouseEventArg = arg; } public ClickItemRoutedEventArgs() : base() { Item = null; Index = -1; MouseEventArg = null; } } //Регистрация события public static readonly RoutedEvent ClickItemEvent = EventManager.RegisterRoutedEvent("ClickItem", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CustomBinView)); //Контейнер добавления-удаления подписчиков public event RoutedEventHandler ClickItem { add { base.AddHandler(ClickItemEvent, value); } remove { base.RemoveHandler(ClickItemEvent, value); } } //Функция генерации события void RaiseClickItem(object item, int index, MouseButtonEventArgs arg) { ClickItemRoutedEventArgs args = new ClickItemRoutedEventArgs(ClickItemEvent, item, index, arg); RaiseEvent(args); } #endregion protected override void OnMouseUp(MouseButtonEventArgs e)//Клик мышкой по контролу { base.OnMouseUp(e); #region Расчёт размеров и проверка условий if ((Values == null) || (Values.Count == 0)) return; double FullWidthCell = RenderSize.Width / (double)(Values.Count); double WidthCell = FullWidthCell; double HeightCell = RenderSize.Height; if ((WidthCell > 2) && (HeightCell > 2)) { WidthCell -= 2; HeightCell -= 2; } Point currentPointCell = new Point(1, 1); #endregion for (int currentNumber = 0; currentNumber < Values.Count; currentNumber++, currentPointCell.X += FullWidthCell) { //Проверка попадания по ячейке if (new Rect(currentPointCell, new Size(WidthCell, HeightCell)).Contains(e.GetPosition(this))) { Values[currentNumber] = !(bool)Values[currentNumber];//ИНверсия значения this.InvalidateVisual();//Вызов рендеринга RaiseClickItem(Values[currentNumber], currentNumber, e);//Генерация события клика } } }
Теперь рассмотрим «Generic.xaml». Здесь описан визуальный стиль (макет) компонента.
<!--Стиль CustomBinView--> <Style TargetType="{x:Type local:CustomBinView}"> <!--Стиль оформления для компонента CustomBinView Задание соответствующим полям в CustomBinView.cs значения по-умолчанию--> <Setter Property="FirstNumber" Value="1"/> <Setter Property="ColorTrue"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="WhiteSmoke" Offset="0" /> <GradientStop Color="Red" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="ColorFalse"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="WhiteSmoke" Offset="0" /> <GradientStop Color="Gray" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="BorderBrush" Value="Black" /> <Setter Property="IsVisibleText" Value="True"/> <Setter Property="FontFamily" Value="Courier New"/> <Setter Property="FontStyle" Value="Normal"/> <Setter Property="FontWeight" Value="Normal"/> <Setter Property="MinHeight" Value="20"/> <Setter Property="MinWidth" Value="50"/> </Style>
Компонент CustomDpkView
Рассмотрим «CustomDpkView.cs». Здесь описаны DependencyProperty и их регистрация.
public class CustomDpkView : Control { public static DependencyProperty ValuesADRProperty; public static DependencyProperty ValuesDATAProperty; public static DependencyProperty ColorTrueProperty; public static DependencyProperty ColorFalseProperty; public const int CNT_ADR_BIT = 8;//Кол-во бит в поле адрес ДПК public const int CNT_DATA_BIT = 24;//Кол-во бит в поле данные static CustomDpkView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDpkView), new FrameworkPropertyMetadata(typeof(CustomDpkView))); #region Регистрация и инициализация ValuesADRProperty ArrayList temp = new ArrayList(CNT_ADR_BIT); for (int i = 0; i < CNT_ADR_BIT; i++) temp.Add(true); ValuesADRProperty = DependencyProperty.Register("ValuesADR", typeof(ArrayList), typeof(CustomDpkView), new PropertyMetadata(temp)); #endregion #region Регистрация и инициализация ValuesDATAProperty temp = new ArrayList(24); for (int i = 0; i < 24; i++) temp.Add(true); ValuesDATAProperty = DependencyProperty.Register("ValuesDATA", typeof(ArrayList), typeof(CustomDpkView), new PropertyMetadata(temp)); #endregion ColorTrueProperty = DependencyProperty.Register("ColorTrue", typeof(Brush), typeof(CustomDpkView)); ColorFalseProperty = DependencyProperty.Register("ColorFalse", typeof(Brush), typeof(CustomDpkView)); } //Значения бит в поле Адрес (8 бит) public ArrayList ValuesADR { get { return (ArrayList)GetValue(ValuesADRProperty); } protected set { SetValue(ValuesADRProperty, value); } } //Значения бит в поле ДАнные (24 бита) public ArrayList ValuesDATA { get { return (ArrayList)GetValue(ValuesDATAProperty); } protected set { SetValue(ValuesDATAProperty, value); } } //Цвет true - значения public Brush ColorTrue { get { return (Brush)GetValue(ColorTrueProperty); } set { SetValue(ColorTrueProperty, value); } } //Цвет false - значения public Brush ColorFalse { get { return (Brush)GetValue(ColorFalseProperty); } set { SetValue(ColorFalseProperty, value); } } #region Программное задание адреса и данных public bool SetValuesADR(ArrayList array) { if (array.Count != CNT_ADR_BIT) return false; ValuesADR = array; return true; } public bool SetValuesDATA(ArrayList array) { if (array.Count != CNT_DATA_BIT) return false; ValuesDATA = array; return true; } #endregion }
Теперь рассмотрим «Generic.xaml». Здесь описан визуальный стиль (макет) компонента, его строение.
<!--Стиль CustomDpkView--> <Style TargetType="TextBlock" x:Key="labelStyle"><!--Стиль оформления для текстовых полей адрес и данные--> <Setter Property="TextAlignment" Value="Center"/> <Setter Property="FontFamily" Value="Comic Sans MS"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontSize" Value="20"/> </Style> <Style TargetType="{x:Type local:CustomDpkView}"><!--Стиль оформления для компонента CustomDpkView--> <Setter Property="Template"> <Setter.Value><!--Структура компонента--> <ControlTemplate TargetType="{x:Type local:CustomDpkView}"> <Grid> <Grid.RowDefinitions><!--Разделение на 4 области (со звёздочкой - плавающее, без - постоянное)--> <RowDefinition Height="30" /> <RowDefinition Height="30*" MinHeight="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30*" MinHeight="30"/> </Grid.RowDefinitions> <!--Текстовое поле Адрес--> <TextBlock Style="{StaticResource labelStyle}" Grid.Row="0" Text="Адрес:"/> <!--Двоичное значение поля Адрес--> <!--Связка полей с помощью TemplateBinding в CustomDpkView.cs с полями компонента CustomBinView--> <local:CustomBinView FirstNumber="1" Grid.Row="1" ColorTrue="{TemplateBinding ColorTrue}" ColorFalse="{TemplateBinding ColorFalse}" Values="{TemplateBinding ValuesADR}"/> <!--Текстовое поле Данные--> <TextBlock Style="{StaticResource labelStyle}" Grid.Row="2" Text="Данные:"/> <!--Двоичное значение поля Данные--> <!--Связка полей с помощью TemplateBinding в CustomDpkView.cs с полями компонента CustomBinView--> <local:CustomBinView FirstNumber="9" Grid.Row="3" ColorTrue="{TemplateBinding ColorTrue}" ColorFalse="{TemplateBinding ColorFalse}" Values="{TemplateBinding ValuesDATA}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> <!--Значение по-умолчанию для поля в CustomDpkView.cs--> <Setter Property="ColorTrue"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="WhiteSmoke" Offset="0" /> <GradientStop Color="Red" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <!--Значение по-умолчанию для поля в CustomDpkView.cs--> <Setter Property="ColorFalse"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="WhiteSmoke" Offset="0" /> <GradientStop Color="Gray" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> </Style>
Использование этих компонентов в Debug_WpfApplication
Рассмотрим «Main.xaml».
<Window.Resources> <!-- Стиль для customDpkView1 --> <Style TargetType="{x:Type ccl:CustomDpkView}" x:Key="dpkViewStyle_1"> <Setter Property="ColorTrue"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="WhiteSmoke" Offset="0" /> <GradientStop Color="Yellow" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="ColorFalse"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="Gray" Offset="0" /> <GradientStop Color="White" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="114" /> <RowDefinition MinHeight="50" Height="114*" /> <RowDefinition Height="50" /> </Grid.RowDefinitions> <ccl:CustomBinView Name="customBinView1" Margin="12,12,12,20" /> <!-- CustomDpkView с пользовательским стилем --> <ccl:CustomDpkView Style="{StaticResource dpkViewStyle_1}" Name="customDpkView1" Grid.Row="1" Height="174" VerticalAlignment="Top" Margin="12,0" /> <TextBlock Name="textBlock1" Text="TextBlock" Grid.Row="2" TextAlignment="Center" FontStretch="Normal" FontFamily="Comic Sans MS" FontWeight="Bold" FontSize="20" Margin="0,0,429,0" /> <Button Content="Button" Grid.Row="2" Name="button1" HorizontalAlignment="Right" Width="75" Click="button1_Click" /> <!-- CustomDpkView со стилем по-умолчанию --> <ccl:CustomDpkView Grid.Row="1" Margin="12,180,12,0" Name="customDpkView2" VerticalAlignment="Top" Height="133" /> </Grid>
P.S.
Ссылка на проект: Скачать проект
Примечание: разработка велась в MS Visual Studio 2010 под «.Net Framework 4»
Жду комментариев и дополнений.
ссылка на оригинал статьи https://habrahabr.ru/post/283124/
Добавить комментарий