C# WPF – Разработка WPF пользовательских компонентов


Недавно пришла мысль в голову: пора переходить на использование WPF! Ну и, соответственно, изучить надо его, чтобы также «виртуозно» использовать, как и WinForms. Заодно и более старшим, консервативным, коллегам доказать, что WPF использовать более эффективно и продуктивно. (И ПО на WPF работает «шутстрее».)
Вот и решил я этой публикацией «мешок зайцев настрелять»:
— Сравнить разработку компонента на WinForms (Ссылка на статью) и WPF;
— Доказать коллегам, что WPF — это продуктивно и эффективно;
— И сделать небольшой простой урок по освоению WPF (особенно для тех, кто привык к WinForms).
В прошлой публикации ссылка я рассказал о разработке компонента на WinForms. Поэтому, цель следующая: сделать такой же «по-смыслу» компонент, но на WPF. В целом, подход такой же и советы для WinForms работают и на WPF.
Забегая вперёд, получился следующий компонент, представленный на рисунке ниже.
b3f601fddc30413386f8f7e72fd3c6d9.png
Компонент (далее — «dpkEditWPF»), так же как и его «собрат» на WinForms является «partial» (разделён на несколько файлов (partial class), для удобства разработки).
b43ccbdf7d4b4f2b80a40de46633fe6e.png
У него также есть Свойство «Значение слова ДПК» (32 разряда) и «Текстовая метка» (чтобы там время отображать). Событие клика по «битовой ячейке» с номером бита также имеется, но только оно стало «маршрутизируемым».

Пример 1
/*Общедоступные свойства, события, генераторы событий*/
    public partial class DpkWordEditWPF
    {
        /// 
        /// Значение слова ДПК (32 разряда)
        /// 
        uint _dpkValue;
        /// 
        /// Свойство - Значение слова ДПК (32 разряда)
        /// 
        public uint DpkValue { get { return _dpkValue; } set { _dpkValue = value; Paint(); InvalidateVisual(); } }
        /// 
        /// Текстовая метка (добавляется к текстовому значению слова ДПК)
        /// 
        string _txtMark;
        /// 
        /// Свойство - Текстовая метка (добавляется к текстовому значению слова ДПК)
        /// 
        public string TextMark { get { return _txtMark; } set { _txtMark = value; Paint(); InvalidateVisual(); } }
        /******/
        /// 
        /// Событие - клик по значению
        /// 
        public event ReturnEventHandler ClickByValue;
        /// 
        /// Генератор события клик по значению
        /// 
        /// номер бита (0-31)
        void OnClickByValue(int index)
        {
            if (ClickByValue != null)
                ClickByValue(this, new ReturnEventArgs(index));
         }
    }



Расчёт размеров dpkEditWPF также — относительный, на основе текущих размеров.

Пример 2
/// 
        /// Установка пропорций
        /// 
        void SetProportions()
        {
            _hPropTextMark = 0.2;
            _hPropAddress = 0.2;
            _hPropAddressBinVal = 0.2;
            _hPropData = 0.2;
            _hPropDataBinVal = 0.2;
        }
        /// 
        /// Установка размеров
        /// 
        void SetSizes()
        {
            /*TextMark*/
            _heightTextMark = RenderSize.Height * _hPropTextMark;
            /*Address*/
            _heightAddress = RenderSize.Height * _hPropAddress;
            /*AddressBinValue*/
            _heightAddressBinVal = RenderSize.Height * _hPropAddressBinVal;
            _widthCellAddressBV = RenderSize.Width / 8.0;
            /*Data*/
            _heightData = RenderSize.Height * _hPropData;
            /*DataBinValue*/
            _heightDataBinVal = RenderSize.Height * _hPropDataBinVal;
            _widthCellDataBV = RenderSize.Width / 24.0;
            /*points*/
            _ptTextMark = new Point(0,0);
            _ptAddress = new Point(0,_heightTextMark);
            _ptAddressBinVal  = new Point(0, _heightTextMark + _heightAddress);
            _ptData  = new Point(0, _heightTextMark + _heightAddress + _heightAddressBinVal);
            _ptDataBinVal  = new Point(0, _heightTextMark + _heightAddress + _heightAddressBinVal + _heightDataBinVal);
        }



Отрисовка компонента также разделена на несколько визуальных буферов и соответственно -процедур отрисовки.

Пример 3
        /// 
        /// визуальный Буфер значения слова ДПК + текстовая метка
        /// 
        DrawingVisual _imgTextMark;
        /// 
        /// визуальный буфер значения адреса
        /// 
        DrawingVisual _imgAddress;
        /// 
        /// визуальный буфер двоичного значения адреса (кликабельные ячейки)
        /// 
        DrawingVisual _imgAddressBinVal;
        /// 
        /// визуальный буфер значения данных
        /// 
        DrawingVisual _imgData;
        /// 
        /// визуальный буфер двоичного значения данных (кликабельные ячейки)
        /// 
        DrawingVisual _imgDataBinVal;


Пример 4
        /// 
        /// Отрисовка области текстового значения Адреса
        /// 
        void PaintAddress()
        {
            if (_imgAddress == null) return;
            using (DrawingContext dc = _imgAddress.RenderOpen())
            {
                dc.DrawRectangle(Brushes.LightBlue, new Pen(Brushes.DarkGray, 1), new Rect(_ptAddress, new Size(RenderSize.Width, _heightAddress)));
                string str = "Адрес: 0x" + (_dpkValue & 0xFF).ToString("X").PadLeft(2, '0');
                DrawTxt(dc, str, _ptAddress, new Size(RenderSize.Width, _heightAddress), Brushes.Black);
            }
        }



Реализация клика по ячейке двоичного значения («битовой ячейке» с номером бита) реализована с помощью обработчика MouseUp, в котором генерируется событие клика по ячейке.

Пример 5
/// 
        /// Обработка клика по ячейке
        /// 
        void DpkWordEditWPF_ClickByValue(object sender, ReturnEventArgs e)
        {
            uint mask = (uint)0x1 << e.Result;
            if ((_dpkValue & mask) > 0)
                _dpkValue &= (~mask); 
            else
                _dpkValue |= mask;
            Paint();
            InvalidateVisual();
        }
        /// 
        /// обработка клика мыши (превращение в клик по ячейке)
        /// 
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);
            Point curPt = e.GetPosition(this);
            /*Клик в области адреса*/
            if ((curPt.X >= _ptAddressBinVal.X) && (curPt.X <= (_ptAddressBinVal.X+RenderSize.Width)))
                if ((curPt.Y >= _ptAddressBinVal.Y) && (curPt.Y <= (_ptAddressBinVal.Y + _heightAddressBinVal)))
                {
                    if ((curPt.X % _widthCellAddressBV) == 0) return;
                    int index = (int)(curPt.X / _widthCellAddressBV);
                    OnClickByValue(index);
                    return;
                }
            /*клик в области данных*/
            if ((curPt.X >= _ptDataBinVal.X) && (curPt.X <= (_ptDataBinVal.X + RenderSize.Width)))
                if ((curPt.Y >= _ptDataBinVal.Y) && (curPt.Y <= (_ptDataBinVal.Y + _heightDataBinVal)))
                {
                    if ((curPt.X % _widthCellDataBV) == 0) return;
                    int index = (int)(curPt.X / _widthCellDataBV);
                    OnClickByValue(index + 8);
                    return;
                }
        }



Масштабирование и вывод на экран сделаны по тому же принципу, что и в компоненте на WinForms. Масштабирование — вызов отрисовки в буфер и вывод на экран. Вывод на экран — отрисовка визуальных буферов.

Пример 6
/// 
        /// первая отрисовка при появлении
        /// 
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            _imgTextMark = new DrawingVisual();
            _imgAddress = new DrawingVisual();
            _imgAddressBinVal = new DrawingVisual();
            _imgData = new DrawingVisual();
            _imgDataBinVal = new DrawingVisual();
            Paint();
            InvalidateVisual();
        }
        /// 
        /// обработка вывода на экран
        /// 
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
            {
                /*режим дизайнера*/
                _dpkValue = 0xFFa42312;
                Paint();
            }
            drawingContext.DrawDrawing(_imgTextMark.Drawing);
            drawingContext.DrawDrawing(_imgAddress.Drawing);
            drawingContext.DrawDrawing(_imgAddressBinVal.Drawing);
            drawingContext.DrawDrawing(_imgData.Drawing);
            drawingContext.DrawDrawing(_imgDataBinVal.Drawing);
        }



Компонент написан на MS Visual Studio 2010, .Net Framework 4. Ссылка на проект: Ссылка на проект
В целом код получился компактнее, чем на WinForms и работает «шустрее». Два тестовых проекта были запущены на машине со следующими характеристиками: Windows XP SP2, процессор 1-ядерный, 1 Гб ОЗУ.
WPF продемонстрировал работу без фризов при изменении размеров формы, WinForms — в свою очередь, изрядно «подтормаживал» при тех же манипуляциях.
P.S. Надеюсь, скоро все проекты на WPF будем на заводе делать.
P.P. S. Трудно бороться с консерватизмом!

© Habrahabr.ru