Как стать автором
Обновить

Комментарии 21

Спасибо, совсем не подумал о 6 шарпе. Добавлю в статью nameof тоже.
НЛО прилетело и опубликовало эту надпись здесь
Какие же уродливые эти юайные фреймворки. Я работал с разными — MFC, WPF, ATL/WTL, VCL, Qt, WinForms и даже античным Turbo Vision в текстовом режиме под DOS. С++, Pascal/Delphi, C#. Все эти связки оставляют ощущение, что твой инструмент просто не предназначен для написания интерфейсов. Где-то все более уродливо, где-то менее, но в общем и целом все чрезвычайно безнадежно. Особенно удручающим все становится, когда сам фреймворк начинает превращается в неиссякаемый источник проблем и уродств.

Ситуация удивительна, ведь рядом с миром десктоп-приложений существует целая вселенная с отличными технологиями для создания интерфейсов — HTML, CSS, ECMAScript-like языки. Все стандартизировано, не нужно каждый раз изучать заново очередной монструозный фреймворк от проекта к проекту. Дкеларативный UI, стили отделены от разметки, UI можно генерировать прямо из XML с помощью XSLT-преобразований, совершенно понятное и простое как пять копеек DOM-дерево, слои, события, canvas, json, поддержка кучи графических форматов и прочие ништяки, облегчающие жизнь. И это все можно использовать для написания desktop-приложений. Проекты, на которых был выбран именно этот подход к написанию UI были самыми приятными из всех, в которых я участвовал. К несчастью, им мало кто пользуется. Но пользуются. Антивирусы Norton, Yahoo Messenger, Nod32, EverNote, линейка продуктов Mozilla (с оговорками).

Но это все придет на десктоп. Недавно nodejs + скрестили с хромиумом, чтобы писать десктопные приложения. Существуют другие движки, специально заточенные под десктопный юай, вроде Sciter'a, которые можно юзать из С++. Windows Store Apps можно писать на HTML/CSS+JS. День, когда вымрет последний UI-фреймоврк и все перейдут на веб-стек, нужно будет объявить праздничным и сделать его выходным.
роекты, на которых был выбран именно этот подход к написанию UI были самыми приятными из всех, в которых я участвовал. К несчастью, им мало кто пользуется. Но пользуются. Антивирусы Norton, Yahoo Messenger, Nod32, EverNote, линейка продуктов Mozilla (с оговорками).

Что забавно, Evernote сперва был написан на WPF, но авторы устали бороться с его тормозами.
В корне не согласен. Веб стек совершенно неюзабелен без траспиляторов, а хоть насколько-то адекватный HTML+CSS только сейчас появляется (flexbox и shadow DOM).

Все стандартизировано
Серьёзно? Это не так, даже если вы пишете под единственный браузер.
не нужно каждый раз изучать заново очередной монструозный фреймворк от проекта к проекту
Да ладно? Вы в 2005 году, чтоль? У нас каждый день новый js фреймворк а-ля react/angular/etc, а каждый месяц «модным» становится другой.
стили отделены от разметки
Без shadow DOM это огромный костыль и тот самый неиссякаемый источник проблем и уродств. В WPF это разделение сделано гораздо лучше.
UI можно генерировать прямо из XML с помощью XSLT-преобразований
Пока вы верстаете свою домашнюю страничку, да. А когда вы находете на SO ответ «модифицируйте HTML» все ваши мечты и идеалы разбиваются об этот ваш любимый HTML+CSS.
совершенно понятное и простое как пять копеек DOM-дерево
Простой, за исключением before, after, стилей по-умолчанию, текстовых нодов. Теперь ещё shadow DOM, не забывайте!
Недавно nodejs + скрестили с хромиумом, чтобы писать десктопные приложения.
Не нещадно тормозящий Electron-редактор появился впервые у майкрософта в виде VSO. 10 вкладок, Notepad++: 5 МБ памяти; 10 вкладок, VSO: 500 МБ памяти.

Такое чувство, что вы троллите тут.

Я буду проклинать тот день, когда разработчики перестанут писать нативные приложения и перейдут на веб стек. Молюсь, чтобы средства разработки подтянулись под уровень веб-средств и порог вхождения стал ниже.

Источник: Писал на Delphi, Qt, WinForms, WPF, а теперь пишу под веб.
Вы просто как-нибудь попробуйте написать нативное приложение, используя для создания UI HTML+CSS+JS, и сразу увидите разницу. Есть очень легкие движки, которые не отжирают по 500 метров. HTML/CSS/JS стандартны и везде примерно одинаковы, в разных движках между ними нет таких различий как между WPF и MFC, и ознакомиться с этими различиями в разы проще, чем переучиваться с WPF на MFC. Плюс они не умрут в ближайшем будущем, их будет проще и дешевле поддерживать, будет легче находить специалистов, понимающих как все работает. DOM это в сотни раз круче, чем ничего, предлагаемое большинством UI фреймворков. Чтобы не возникали вопросы «модифицируйте HTML», составляйте спецификации перед началом проекта. Но даже если придется менять, это будет в разы проще, чем переписывать какой-нибудь Qt-контрол, проще и дешевле. Вы сначала попробуйте, а потом вердикт выносите.
Не нещадно тормозящий Electron-редактор появился впервые у майкрософта в виде VSO. 10 вкладок, Notepad++: 5 МБ памяти; 10 вкладок, VSO: 500 МБ памяти.

А что будет с таким редактором на WPF? :-) Сколько секунд он будет запускаться, жрать памяти и насиловать видеокарту?
PS: редактор на WPF — вытекут глаза от шрифтов.
Почему-то от Visual Studio глаза не вытекают ;)
1 — UserControl'ы в принципе зло, лучше писать на кастом контролах т.к. проблема с RD применима ко всему XAML'у UC в целом.
2 — не имеет отношение к WPF, к тому же тот же Resharper подскажет, что не стоит лямбдой отписываться
3 — интересно. хотя зачем делать Mode=TwoWay для свойства у класса, не реализующего INPC?
5 —
при переименовании свойств можно забыть изменить константы в условиях
опять же, решарпер выскажет подозрения при переименовании и предложит переименовать эту константу тоже. А вообще такой код конечно плохой, лучше использовать RX :-)
6 —
Время, затраченное Dispatcher.Invoke сверх «полезной» нагрузки при вызове из того же потока, к которому принадлежит Dispatcher — 0.2 мкс на один вызов.
Тот же показатель, но при вызове из другого потока — 26 мкс на вызов.

Надеюсь, вы понимаете что есть еще и BeginInvoke, но всё же мои тесты с Invoke:
с UI потока: 0.0002 мкс
с потока тредпула: 0.0004 мкс (специально перевел 4000 тиков в микросекунды, чтобы показать разницу с вашими)
ЧЯДНТ? Вот так тестил: gist.github.com/EgorBo/0659e8e1b42ea72190b7

8 — я бы ваш ModalDialogHelper переписал на TaskCompletionSource'ы без вот такой жести: Task.Run(() => waitEvent.WaitOne()); и AutoResetEvent'ов в целом.

Моё мнение — WPF не нужен: в интерпрайзах перешли уже на веб давно (а неинтерпрайз на нем и не писали особо), а wpf так и остался тормозным, с мыльными шрифтами на офисном dpi и его не обновляли уже лет 5 (блог wpf команды через 5 лет бездействия проснулся 4 месяца назад с какими-то невнятными обещаниями и опять заснул) с монструозным XAML. Если уж надо делать неинтерпрайзный клиент чего-то нужного для десктопа, то современные реалии требуют и поддержку Mac OS, с чем у WPF чуть лучше чем никак.
Виноват, перепутал мкс с мс, корретные результаты в мкс такие (что все равно много меньше чем у вас для Invoke):
с UI потока: 0.2 мкс
с потока тредпула: 0.4 мкс
1. Делать совсем кастомные контролы для вьюшек и панелей — перебор, для этого UC как раз и предназначены. Делать просто элементы управления типа кастомных комбо-боксов и прочих с помощью UC — да, неправильно.
2.1. Я так и написал, что не имеет отношения непосредственно к WPF, но сказать про эту проблему стОит.
2.2. С TwoWay в примере я погорячился, там лучше написать OneWay, так как это наиболее частый (и, по итогу, проблематичный) сценарий. Подправлю.
5. Не у всех есть Решарпер. А касательно Rx — да, вещь хорошая.
6. В ближайшее время перемеряю и выложу свои тесты. Да, про BeginInvoke, конечно, знаю.
8. Надо подумать

Насчет того, что энтерпрайз давно перешел на ВЕБ — заблуждение, я вижу как минимум 5 больших проектов для очень крупных компаний, которые используют либо только WPF, либо микс WPF и WEB решений для своих нужд. Полностью переходить на ВЕБ никто из них пока не собирается. Не энтерпрайз — тоже немало десктопных утилит используют сейчас WPF. Не стОит забывать, что разработка под Windows 8 и Windows Phone использует те же принципы и некоторые из перечисленных проблем актуальны и там.
Перемерял — результаты у меня особо не изменились — вот код:

        private void TestPerformance(object param)
        {
            Task.Run(() => TestPerformanceInternal());
        }

        private void TestPerformanceInternal()
        {
            int iterations = 1000000;

            var sw = new Stopwatch();
            var csw = new Stopwatch();

            sw.Start();
            for (int i = 0; i < iterations; i++)
            {
                csw.Start();
                _dispatcher.Invoke(() =>
                {
                    csw.Stop();
                });
            }
            sw.Stop();

            long crossThreadTiming = csw.ElapsedMilliseconds;
            long invokeTiming = sw.ElapsedMilliseconds;

            MessageBox.Show(string.Format("Cross-thread timing per call: {0} mcs\nFull invoke timing per call: {1} mcs",
                (double)crossThreadTiming * 1000 / iterations,
                (double)invokeTiming * 1000 / iterations));
        }


Резльутат:
Cross-thread timing per call: 14,762 mcs
Full invoke timing per call: 20,688 mcs

В статье я имел в виду вторую цифру. В этот раз она получилась чуть меньше 26 мкс, но порядок тот же.
Насчет TaskCompletionSource — спасибо за идею, реализация получилась проще, обновил статью.
Утечки на событиях

Можно реализовать специализированный WeakEventManager, который даст возможность подписываться на изменение конкретного свойства у INPC.
Примеры гуглятся по «Custom Weak Event Manager WPF»
Я про это тоже писал ;)
Объяснение реализации паттерна Weak Events выходит за рамки этот статьи, поэтому просто укажу ссылку, где эта тема рассмотрена очень подробно: www.codeproject.com/Articles/29922/Weak-Events-in-C.
Действительно, есть такое дело. Подписка-отписка в итоге получается еще более громоздкими, т.к. может понадобиться много на что подписаться. А правильная реализация Weak Event сама по себе многострочна, но в одном месте, а дальше чистый реюз.
3. А вот так? Должно в дизайнере нормально работать.
public class CustomComboBox : ComboBox
{
    	public CustomComboBox()
	{
		SetResourceReference(StyleProperty, typeof(ComboBox));
	}
}
Отличное решение, спасибо! Добавлю в статью.
1. Насчет ошибок при биндинге, их можно поймать, добавив в биндинг опцию: PresentationTraceSources.TraceLevel=«High»
2. Насчет нотификации при изменении properties во ViewModel. Придерживаюсь следующего подхода: ВСЕ поля, на которые идет биндинг, делать как dependency-properties. Не важно, идет ли речь о юзерском контроле, или нет. Эта идеалогия вытекает из изпользуемого мною (сначала на работе в enterprise-проектах, теперь и во всех личных проектах) фреймворка Catel, где даже Model содержит dependency properties. Если кратко, это, вкупе с другими фишками Catel позволяет быстро создавать WPF-приложения с удобной моделью валидации и обновления UI на лету, интерфейс получается реактивный, а код понятный, т.к. на уровне фреймворка реализовано множество MVVM-примитивов.
Зависимые свойства в Catel объявляются немного иначе, примерно так:

public Book SelectedBook
{
get { return GetValue(SelectedBookProperty); }
set { SetValue(SelectedBookProperty, value); }
}
public static readonly PropertyData SelectedBookProperty = RegisterProperty(«SelectedBook», typeof(Book));

Есть сниппет, т.е. писать все это не приходится, буквально пара нажатий и готово. Если даже это смущает, то Catel.Fody позволяет прикрутить это в runtime к обычным auto-properties.

P.S. если интересны подробности, то легкая вводная здесь:
>>> Для правильного решения задачи нужно объявить отдельный метод, поместить в него установку IsModified и использовать в качестве обработчика, как всегда и делалось до появления лямбда-выражений в C#.

До появления лямбда-выражений в C# уже существовали замыкания, хоть и в другом синтаксисе. Если использовать отдельный метод, то не получится замкнуть «локальные» переменные метода.

Можно делать примерно так так:

PropertyChangedEventHandler handler = (sender, e) => { /* Capturing some local variables. */ };
Entity.PropertyChanged += handler;
IDisposable subscriptionToken = Disposable.Create(() => { Entity.PropertyChanged -= handler; });
_compositeDisposable.Add(subscriptionToken);

Нам не нужно «помнить» метод, чтобы от него отписаться в Dispose(). Мы определяем отписку сразу же при подписывании лямбдой, просто откладываем её на потом.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.