[Из песочницы] C# WinForms — Советы по разработке пользовательских компонентов

сегодня в 13:40

Предисловие


Основная моя специализация — разработка ПО для систем реального времени на базе микроконтроллеров. Но иногда и на C# WinForms бывают разработки настольных приложений с весьма специфичными пользовательскими интерфейсами. Например, двоичное отображение слова по интерфейсу ДПК, представлено на рисунке ниже.

image

Вот такой компонент пришлось изобрести для привычного пользователям отображения данных.

Хочу поделиться несколькими «эмпирическими» советами по разработке пользовательских компонентов, которые обеспечивают более быструю и организованную разработку оптимизированных визуальных (графически нагруженных) пользовательских компонентов.
Приведу несколько советов с практической демонстрацией.

Совет первый


Рендеринг (отрисовку) содержимого UserControl«а производить во внутренний визуальный буфер Bitmap.
Пример 1
       
        /// 
        /// Общий визуальный буфер контрола
        /// 
        Bitmap _imgControl;
        /// 
        /// Визуальный буфер поля текст
        /// 
        Bitmap _imgDPKText;
        /// 
        /// поля значения Адрес
        /// 
        Bitmap _imgDPKAdrVal;
        /// 
        /// Визуальный буфер поля Адрес
        /// 
        Bitmap _imgDPKFieldAdr;
        /// 
        /// Визуальный буфер поля дата
        /// 
        Bitmap _imgDPKFieldData;
        /// 
        /// поля значения дата
        /// 
        Bitmap _imgDPKDataVal;
        double TT = 0.2;
        double AT = 0.2;
        double AVT = 0.2;
        double DT = 0.2;
        double DVT = 0.2;
        /// 
        /// Отрисовка визуального буфера контрола
        /// 
        void Render()
        {
            if (_word == null) return;
            _imgControl = new Bitmap(_widthImgControl, _heightImgControl);
            using (Graphics g = Graphics.FromImage(_imgControl))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
                g.Clear(Color.White);
                int shiftY = 0;
                g.DrawImage(_imgDPKText, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * TT);
                g.DrawImage(_imgDPKFieldAdr, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * AT);
                g.DrawImage(_imgDPKAdrVal, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * AVT);
                g.DrawImage(_imgDPKFieldData, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * DT);
                g.DrawImage(_imgDPKDataVal, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * DVT);
            } 
        }


А обработку события Paint организовывать таким образом, чтобы обеспечить только вывод визуального буфера с содержимым, а не производить рендеринг заново.
Пример 2
        /// 
        /// Вывод визуального буфера на экран
        /// 
        void DPKWordEdit_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(Color.White);
            if (_imgControl == null)
            {
                e.Graphics.DrawRectangle(new Pen(_clrBorder), new Rectangle(new Point(0,0), new Size(this.Width-1, this.Height-1)));
                return;
            }
            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            e.Graphics.DrawImage(_imgControl, new Rectangle(new Point(0,0), this.Size));
        }


Совет второй


Данный совет касается реализации масштабирования. Есть 2 пути:
1. Производить рендеринг содержимого компонента на основе новых размеров. (Это ресурсоёмко!)
2. Производить вывод визуального буфера без повторного рендеринга. (Масштабируя сам буфер Bitmap!)
Пример 3
        /// 
        /// Обработка изменения размеров 
        /// 
        void DPKWordEdit_Resize(object sender, EventArgs e)
        {
            Refresh();
        }       

        e.Graphics.DrawImage(_imgControl, new Rectangle(new Point(0,0), this.Size));


Совет третий


Данный совет касается рендеринга содержимого UserControl«a. Когда компонент получается «сложный», состоящий из множества различных элементов интерфейса, имеет смысл производить рендеринг в нескольких визуальных буферах (несколько логически разных области компонента).
Пример 4
        /// 
        /// Общий визуальный буфер контрола
        /// 
        Bitmap _imgControl;
        /// 
        /// Визуальный буфер поля текст
        /// 
        Bitmap _imgDPKText;
        /// 
        /// поля значения Адрес
        /// 
        Bitmap _imgDPKAdrVal;
        /// 
        /// Визуальный буфер поля Адрес
        /// 
        Bitmap _imgDPKFieldAdr;
        /// 
        /// Визуальный буфер поля дата
        /// 
        Bitmap _imgDPKFieldData;
        /// 
        /// поля значения дата
        /// 
        Bitmap _imgDPKDataVal;


        /// 
        /// Отрисовка в визуальный буфер
        /// 
        void DPKWordEdit_Rendering(object sender, EventArgs e)
        {
            RenderDPKText();
            RenderDPKFieldAdr();
            RenderDPKValAdr();
            RenderDPKFieldData();
            RenderDPKValData();
            Render();
            Refresh();
        }


А затем их объединять в визуальный буфер UserControl«а.
Пример 5
        /// 
        /// Отрисовка визуального буфера контрола
        /// 
        void Render()
        {
            if (_word == null) return;
            _imgControl = new Bitmap(_widthImgControl, _heightImgControl);
            using (Graphics g = Graphics.FromImage(_imgControl))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
                g.Clear(Color.White);
                int shiftY = 0;
                g.DrawImage(_imgDPKText, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * TT);
                g.DrawImage(_imgDPKFieldAdr, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * AT);
                g.DrawImage(_imgDPKAdrVal, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * AVT);
                g.DrawImage(_imgDPKFieldData, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * DT);
                g.DrawImage(_imgDPKDataVal, new Point(0, shiftY)); shiftY += (int)(_heightImgControl * DVT);
            } 
        }


Написан компонент на Visual Studio 2010 под .Net Framework 2.0 на WinForms.

Проект компонента и демонстрационный проект можно скачать по ссылке: cloud.mail.ru/public/JKhH/oGPrX4DFX.

© Habrahabr.ru