Почему появилась эта публикация?
Недавно пришла мысль в голову: пора переходить на использование WPF! Ну и, соответственно, изучить надо его, чтобы также «виртуозно» использовать, как и WinForms. Заодно и более старшим, консервативным, коллегам доказать, что WPF использовать более эффективно и продуктивно. (И ПО на WPF работает «шутстрее».)
Вот и решил я этой публикацией «мешок зайцев настрелять»:
— Сравнить разработку компонента на WinForms (Ссылка на статью) и WPF;
— Доказать коллегам, что WPF – это продуктивно и эффективно;
— И сделать небольшой простой урок по освоению WPF (особенно для тех, кто привык к WinForms).
Изучение WPF для тех, кто с WinForms
В прошлой публикации ссылка я рассказал о разработке компонента на WinForms. Поэтому, цель следующая: сделать такой же «по-смыслу» компонент, но на WPF. В целом, подход такой же и советы для WinForms работают и на WPF.
Забегая вперёд, получился следующий компонент, представленный на рисунке ниже.
Компонент (далее – «dpkEditWPF»), так же как и его «собрат» на WinForms является «partial» (разделён на несколько файлов (partial class), для удобства разработки).
У него также есть Свойство «Значение слова ДПК» (32 разряда) и «Текстовая метка» (чтобы там время отображать). Событие клика по «битовой ячейке» с номером бита также имеется, но только оно стало «маршрутизируемым».
/*Общедоступные свойства, события, генераторы событий*/
public partial class DpkWordEditWPF
{
/// <summary>
/// Значение слова ДПК (32 разряда)
/// </summary>
uint _dpkValue;
/// <summary>
/// Свойство - Значение слова ДПК (32 разряда)
/// </summary>
public uint DpkValue { get { return _dpkValue; } set { _dpkValue = value; Paint(); InvalidateVisual(); } }
/// <summary>
/// Текстовая метка (добавляется к текстовому значению слова ДПК)
/// </summary>
string _txtMark;
/// <summary>
/// Свойство - Текстовая метка (добавляется к текстовому значению слова ДПК)
/// </summary>
public string TextMark { get { return _txtMark; } set { _txtMark = value; Paint(); InvalidateVisual(); } }
/******/
/// <summary>
/// Событие - клик по значению
/// </summary>
public event ReturnEventHandler<int> ClickByValue;
/// <summary>
/// Генератор события клик по значению
/// </summary>
/// <param name="index">номер бита (0-31)</param>
void OnClickByValue(int index)
{
if (ClickByValue != null)
ClickByValue(this, new ReturnEventArgs<int>(index));
}
}
Расчёт размеров dpkEditWPF также – относительный, на основе текущих размеров.
/// <summary>
/// Установка пропорций
/// </summary>
void SetProportions()
{
_hPropTextMark = 0.2;
_hPropAddress = 0.2;
_hPropAddressBinVal = 0.2;
_hPropData = 0.2;
_hPropDataBinVal = 0.2;
}
/// <summary>
/// Установка размеров
/// </summary>
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);
}
Отрисовка компонента также разделена на несколько визуальных буферов и соответственно -процедур отрисовки.
/// <summary>
/// визуальный Буфер значения слова ДПК + текстовая метка
/// </summary>
DrawingVisual _imgTextMark;
/// <summary>
/// визуальный буфер значения адреса
/// </summary>
DrawingVisual _imgAddress;
/// <summary>
/// визуальный буфер двоичного значения адреса (кликабельные ячейки)
/// </summary>
DrawingVisual _imgAddressBinVal;
/// <summary>
/// визуальный буфер значения данных
/// </summary>
DrawingVisual _imgData;
/// <summary>
/// визуальный буфер двоичного значения данных (кликабельные ячейки)
/// </summary>
DrawingVisual _imgDataBinVal;
/// <summary>
/// Отрисовка области текстового значения Адреса
/// </summary>
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, в котором генерируется событие клика по ячейке.
/// <summary>
/// Обработка клика по ячейке
/// </summary>
void DpkWordEditWPF_ClickByValue(object sender, ReturnEventArgs<int> e)
{
uint mask = (uint)0x1 << e.Result;
if ((_dpkValue & mask) > 0)
_dpkValue &= (~mask);
else
_dpkValue |= mask;
Paint();
InvalidateVisual();
}
/// <summary>
/// обработка клика мыши (превращение в клик по ячейке)
/// </summary>
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. Масштабирование – вызов отрисовки в буфер и вывод на экран. Вывод на экран – отрисовка визуальных буферов.
/// <summary>
/// первая отрисовка при появлении
/// </summary>
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();
}
/// <summary>
/// обработка вывода на экран
/// </summary>
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. Трудно бороться с консерватизмом!
Комментарии (32)
WNeZRoS
12.09.2015 19:16+2Статья должна называться не «Разработка WPF пользовательских компонентов», а «Как быстро портировать WinForms компонент на WPF»
Athari
12.09.2015 23:38+6Неканонично. Слепое портирование с WinForms.
public uint DpkValue { get { return _dpkValue; } set { _dpkValue = value; Paint(); InvalidateVisual(); } }
Следует использовать dependency property с метаданными, которые предписывают перерисовать содержимое. Ну или цепочкой зависимостей приводить к изменению таких свойств.
Никаких методов Paint быть не должно, такого понятия не существует.
public event ReturnEventHandler<int> ClickByValue;
Следует использовать RoutedEvent, чтобы события могли роутиться.
А дальше уже совсем вакханалия пошла с ручной отрисовкой. Вам на что дадены биндинги, шаблоны, стили, триггеры и прочее? Не, круто, вы изменили названия классов с System.Windows.Forms на System.Windows. Но при чём здесь WPF?
NeoNN
13.09.2015 11:18+1Не знаю, есть ли смысл в таких статьях — с одной стороны, да, они показывают какие-то изменения к лучшему, но с другой стороны, использовать технологию без всех ее преимуществ — это как забивать гвозди микроскопом. Приходилось видеть некоторые такие проекты (и, о ужас, в начале профессиональной деятельности даже писать самому) и считаю, что для успешной работы все-таки сначала надо подтянуть матчасть, а потом уже применять технологию в production — иначе код со временем превратится в адовый ад и придется все переписывать.
ZOXEXIVO
13.09.2015 11:49+3Если сделать на WPF приложение не понимая смысла в WPF, то можно нарваться на ситуации, когда все будет тормозить и загружать любой процессор на 80%.
После современного Web такие приложения уже кажутся динозаврами, даже если они и используют WPF.
cyber_roach
13.09.2015 19:07+2У нас целая тонна наработок по контролам WPF различной сложности.
От всяких спидометров, джойстиков, контролов для пультов управления умных домов, кастомных ListView поддерживающих анимацию, игровых элементов и пр. до собственного окна с производительной прерастиризованной анимацией у навигации (как в WinStore приложениях)
И главное — мы умеем потом это стилизовать в XAML, собственно без нормального XAML интерфейса, контрол на WPF теряет смысл.
Если у аудитории есть интерес, могу попробовать что-то написать
Извиняюсь за большие картинки под спойлером, нарезать поленился
Athari
15.09.2015 05:36Пишите, нормальных технических статей на Хабре никогда не бывает слишком много.
darked
15.09.2015 13:03+1Большая просьба написать!!! В инете так мало толково по WPF, где рисуют что — то посложнее треугольников и закрашенных кругов.
CrazyViper
13.09.2015 20:42+1Недавно пришла мысль в голову: пора переходить на использование WPF!
Сначала прочитал это предложение как стеб, но почитал дальше и понял, что нет. Все уже давно обсуждают умер ли WPF или все еще жив, а вам только пришла в голову мысль переходить на него? Поздновато уже его начинать изучать — вам проще дождаться когда он окончательно умрет и перейти на следующую технологию.
Ну и выше уже написали, что вы просто заменили названия классов с одного пространства имен на другое. Советую почитать про DataBinding, стили и прочее. Тогда ваш код станет еще более лаконичным и сможете коллегам доказать.lightman
13.09.2015 21:01+1А какие-то технологии уже прогнозируются ему на замену?
CrazyViper
13.09.2015 21:13Microsoft пока хранит молчание и говорит что для десктопов главная технология — это WPF. Но этой технологии уже почти 10 лет. Да, она хороша и выполняет свои задачи на отлично. Но сейчас десктопы — это узкий сегмент рынка корпоративных систем. Все остальное — это веб/мобаил. Да и java постепенно перебирается на рынок десктопных приложений.
NeoNN
13.09.2015 21:10А кто его хоронит? В Windows Store тот же самый (ну ладно, почти тот же самый) XAML, всякие MVVM с калибурнами и Behaviors. Плюс эта тема недавно подробно освещалась в одной из передач на .net rocks, и на замену WPF для десктопных приложений вроде как пока ничего не предвидится.
withkittens
13.09.2015 21:15+1WPF != XAML. WPF и Windows Store обе используют XAML, но под капотом они разные.
В конце концов, XAML — это просто язык разметки.NeoNN
14.09.2015 10:47Вы можете привести несколько примеров кардинальной разницы между разработкой wpf и win store приложения? Я говорю здесь о разработке views и view models. Я понимаю, что подмножества языка разные, но чем отличается процесс рендеринга в том и другом случае, например? Цикл Measure-arrange тот же самый, стили и шаблоны, поведения и триггеры, которые, конечно, беднее, но они есть. MVVM используется точно таким же образом, Caliburn.micro ставится из nuget. Blend работает. Итак, в чем же существенная разница для разработчика?
withkittens
14.09.2015 11:13+1Между разработкой на Wpf и WinRT/UAP кардинальной разницы нет. Можно сказать, в большинстве случаев просто отличаются пространства имён, синтаксис там и сям, где-то плюс фича, где-то минус.
А вот рендеринг и технологии под капотом разные. Wpf был создан во времена DirectX 9 и, хоть он GPU-accelerated, рендеринг неоптимизирован и очень груб. WinRT Xaml же — это Direct2D (появившийся, если не ошибаюсь, в WIndows 8). То, каким бы мог быть Wpf.
Есть отличная статья на эту тему — A Critical Deep Dive into the WPF Rendering System (хабрахабровский перевод).Ilya81
22.09.2015 15:32По указанной в статье причине как раз для вспомогательных геометрических объектов я всегда использую OnRender. Хотя, о том, как работает DrawingContext в статье почти не написано. Но если это не просто какие-то линии и фигуры, а они должны принимать нажатия мыши и т. п. случаи, т. е. когда их проще делать элементами пользовательского интерфейса, и их нужно больше примерно 10-и — вот тут и обнаруживается слабое место WPF — то, что он шлёт их в память GPU по одному.
CrazyViper
13.09.2015 21:23Хоронить его пытаются все кому не лень. Уже лет 5 как. Но я пока сам на нем пишу и не жалуюсь — он стабилен, багов мало, в корпоративном сегменте еще лет 10 проживет. Но начинать с WinForms переходить на WPF сейчас, когда саму WPF уже почти 10 лет стукнуло, это странно.
Что касается Windows Store — да, XAML тот же. Но это просто язык разметки, все равно что сказать, что WinForms и WPF — это тот же самый C#. Да, паттерны они туда перетащили, но начинка разная и когда пишешь серьезные приложения, то в начинку надо погружаться всегда.Athari
15.09.2015 05:47Но начинать с WinForms переходить на WPF сейчас, когда саму WPF уже почти 10 лет стукнуло, это странно.
А какие варианты? Оставаться на WinForms? Переходить на UWP? Забить на C# и уходить на Qt5? Собственно, сейчас WPF — самый актуальный гуёвый фреймворк на дотнете для десктопа. Вариантов нет.
Это не столько WPF умирает, сколько мелкомягкие забили на десктоп в целом.withkittens
15.09.2015 11:24+2Не забили, просто стали мыслить в другом направлении.
1. Они выпустили W10, в которой приложения Windows Store и UAP способны запускаться в привычных для каждого окошках.
2. Они назвали W10 последней версией и всячески пытаются переманить всех на неё (особенно народ с W7).
Косвенно это означает, что популярность и востребованность UAP-приложений со временем будет расти.
Вот, пожалуйста, получаем новый десктоп. (А ещё мобильники и планшеты, почти бесплатно.)
Есть конечно определённая проблема в том, что UAP-приложения сидят в песочнице, т.е. какие-то системные утилиты не поделать. Но, например, энтерпрайз, который использует именно UAP, я могу себе представить.Athari
15.09.2015 15:05«Метрошный» калькулятор теперь стал выглядеть менее глупо, но удобным, как старый калькулятор, он не стал. У «универсальных» приложений проблема не в окошечности, а в ориентированности под тач и ущербной функциональности по сравнению с нормальным десктопом. От того, что приложения для тача стало возможно использовать на десктопе, принципиально в них ничего не поменялось. Невозможно сделать так, чтобы интерфейс был одновременно удобным и функциональным и для тача, и для мыши.
Скажем так, я буду всерьёз воспринимать «универсальные» приложения, когда Adobe портирует на эту платформу Photoshop. И не ту игрушку, напоминающую инстаграм по функциональности, а полноценный взрослый фотошоп.
Энтерпрайзу эти универсальные приложения тоже нафиг не нужны. Если приложение должно работать и на планшетах, и на мобилках, и на десктопах, и на прочих устройствах (скажем, гуй для заказа в ресторане, которым могут пользоваться официанты и клиенты) — да, универсальные приложения выглядят как удобная платформа. Но вот гуй для менеджеров со стандартным нагромождением гридов портировать под урезанные приложения для тача, где ни черта не работает — кому оно надо?
xtraroman
14.09.2015 13:20-1И WinForms и WPF хорошо прекрасно подходят для таких проектов как этот. Разница лишь в том, что если в WinForms проекте что-то не работает, при падении получите честный колстек проблемы. Если есть проблемы в WPF проекте, придется сидеть и думать, потому что проблема может быть в разметке, в асинхронном вызове — их очень много там. А еще упомянутые выше MVVM фреймворки делают инъекции в код, что тоже усложняет диагностику. Вобщем, если нравится медитировать над кодом со словами «Что же я сделал не так» — ваш выбор WPF.
Athari
15.09.2015 05:43+1Не надо медитировать. Биндинги трейсятся, визуальные деревья со всеми свойствами просматриваются, инъекции логируются и так далее. Да, это несколько отличается от последовательности «поймал исключение — посмотрел в стек», но инструментов для отладки достаточно.
xtraroman
15.09.2015 11:04-1Не буду ходить далеко. Смотрим сорцы этого проекта. Все на OnRender рисуется — в визуальном дереве ничего не увидите :). Кроме биндингов в разметке может быть много проблем всяких, разобраться в которых можно только прочитав немаленький объем исходного кода WPF — медитировать. Вот несколько примеров: определить почему отвалилась виртуализация, определить откуда прилетел Measure.
Athari
15.09.2015 16:01+1Ручной рендеринг используется в исключительных случаях, когда очень критична производительность. Часто это пишется не вручную, а берётся из чужих библиотек. Скажем, всякие графики так оптимизируют, но об этом голова разработчика приложения не болит.
Проблемы с виртуализацией и лишними вычислениями — это оптимизация, а нужда в ней возникает не так уж часто (хотя от приложения зависит, да). Можно считать это платой за мощь.
Скажем, если приложение состоит из нескончаемых гридов, то разницы между WinForms и WPF примерно ноль. Если же в приложении дикое разнообразие современных интерфейсов, то в случае WinForms в коде будет совершенно нечитаемое месиво, а в WPF можно будет хоть что-то понять, и кода будет меньше.
Взять этот же метод, рисующий всё вручную. В WinForms из такой нечитаемой и невизуализируемой каши будет состоять весь код сложных контролов. Да, если глюк где-то среди этой каши, то у вас будет идеальный стек и никакой магии, но насколько будет приятно эту кашу разбирать?xtraroman
15.09.2015 19:15Наверное, непонятно написал: Я утверждаю что в WPF сложнее диагностировать проблемы. По разным причинам: он больше, сложнее, он работает в нескольких потоках, больше тулзней которые надо знать, больше особенностей в поведении стандартных элементов которые надо знать. Так же я привел несколько примеров когда сложно диагностировать причины проблемы.
А вы пытаетесь доказать здесь что разработчику приложения никогда не придется заглядывать «под капот». Это не так.
A1ien
Весь кайф в разработке на WPF раскрывается при использовании паттерна MVVM. А если воспользоваться еще и такой штукой как Caliburn.Micro то разработка вообще превращается в сплошное удовольствие:)
ПС: да, и в вашем случае пользоваться кастом отрисовкой вообще нет ни какой необходимости, для этого в WPF существуют стили, DataTemplates и другие механизмы.
BlackEngineer
Да, согласен в WPF много хороших и удобных вещей, но в данном случае они не нужны…
1. Пример — простой и призван показать лёкгость и простоту освоения (особенно для тех, кто с WinForms)
2. Скорость работы (чем больше xaml кода, тем медленнее (нативный код c# всё-таки быстрее))
3. Ну и не освоил я ещё MVVM — только изучаю
A1ien
Начните с переписывания вашего кода без кода рендеринга, и обработки низкоуровнего события от мыши. Его можно переписать на списко объектов, каждый из которых отвечает за представление бита(элемента) этот список при помощи байндинга привязать к какому нибуть итем контейнеру(ListBox) например, и определить DataTemplate для этого объекта и ControlTemplate для ListBox. Это и будет вашим первым погружением в MVVM :)
A1ien
Вот, накрапал маленький пример.
github.com/AlexDovgan/ShortWpfExample
BlackEngineer
Благодарю
impwx
2. Это распространенное заблуждение. Разница в скорости если и будет заметна, то только на супер-сложных интерфейсах. В вашем случае вариант с идиоматической XAML-разметкой и биндингами ничуть не проиграл бы в скорости, зато код был бы на порядок чище и легче для поддержки.
3. Стоило сначала более подробно изучить окружающие инструменты и подходы к разработке, а не кидаться писать обучающую статью на Хабр. Спешка тем более неуместна, учитывая что WPF существует уже десять лет.