Мне последнее время приходится заниматься интерфейсом приложения для анализа данных, визуальным представлением данных на разных слоях анализа, а также навигацией по слоям и по данным. Соответственно, пришлось разбираться c новомодным WPF, который продвигает концепцию реализующую возможность параллельной (независимой) работы дизайнеров, занимающихся внешним видом приложения и разработчиков, реализующих поведение приложения. Дизайнер, конечно, из меня не очень (мягко говоря), а вот с реализованной концепцией программирования внешнего вида элементов управления в приложениях, мне, кажется, удалось разобраться. Собственно, вот этим пониманием некоторых аспектов концепции я и хочу поделиться, в том числе для того, чтобы расширить это понимание по результатам критики и/или обсуждения.

Возможно, кому‑то будет интересно сравнить то, что предлагает WPF, с концепциями реализованными в JavaFX.


Откуда берутся проблемы при работе с визуальными библиотеками

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

Для разработки собственных визуальных представлений для десктопных задач навигации по инженерным данным мы выбрали технологии WPF. Собственно, у нас не было большого выбора, поскольку проект реализуется под Windows, и большая часть кода выполнена на C#. По результатам того, что уже сделано, можно вполне уверенно сказать, что, несмотря на некоторые трудности освоения технологии WPF в начале, мы совершенно не разочаровались в ней. Более того, функциональность и возможности WPF поражают воображение, и это, зачастую, является причиной одной очень парадоксальной, как мне кажется, проблемы. Каждая конкретная задача визуализации, компоновки, использования комбинации визуальных управляющих элементов (контролов) имеет чуть ли не десятки решений, выбрать из которых одно, самое лучшее, кажется, не представляется возможным.

В заголовок я вынес только упоминание WPF темплейтов, но на самом деле разнообразие возможностей, с которым приходится сталкиваться, касается любых WPF сущностей и всех их комбинаций, это темплейты, стили, тригеры, визуальные состояния, поведения, анимации, трансформации, это из того чего мне довелось касаться, но я думаю это не полный список.

Актуальность технологии

Так же как язык С незаменим для разработки и обновления компонентов ядра операционной системы, так и WPF теперь не заменим (как мне кажется) для разработки визуального оформления — фронтенда операционной системы. Я даже не постесняюсь предположить, что WPF и XAML станут такой же бессмертной технологией, по крайней мере, в Windows, как и язык С на уровне ядра операционной системы.

Можно привести большой список как проприетарных, так и OpenSource проектов, которые используют и развивают WPF технологии, успешно выпускают обновления и составляют планы на будущее относительно WPF. Для примера:

DevExpress

Telerik

Xceed

caliburn.micro

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

Контролы лишенные внешнего вида

Наверно мало кто обращает внимание, что, во времена господства WinForm, все кнопки в основном были прямоугольными. А вот чтобы создать какую‑то особенную кастомную кнопку, надо было искать особенную визуальную библиотеку. WPF решает проблему определения формы кнопки и любого контрола, и даже целого окна кардинально и, видимо, навсегда.

Дополнение про «навсегда» здесь не означает, что не будет какой‑то новой технологии или фреймворка, или <‑придумайте свое название чего→ которые сделают процесс определения формы контрола более эффективным в каком‑то смысле. Слово «Навсегда» здесь означает, что эта проблема один раз уже решена, и вряд ли в ближайшей перспективе можно будет придумать какой‑то принципиально новый способ решения этой проблемы. Тем более что мы видим, что и в экосистеме вокруг Java, например, предлагается во многом похожая технология JavaFX.

Тут надо сделать одно пояснение относительно определения «лишенный внешнего вида» элемент. Элемент создается и предоставляется с внешним видом по умолчанию — то есть вы можете его сразу использовать с тем внешним видом, с которым он изначально разработан — концепция предоставляет вам возможность изменять внешний вид по своему усмотрению. Отсутствие внешнего вида надо понимать именно в том смысле, что вы не всегда знаете, с каким исходным внешним видом элемент был создан.

Давайте же посмотрим, каким образом эта проблема управления формой и компоновкой контрола решается в WPF на примере из учебника по WPF.

На рисунке приведен классический пример управления внешним видом элемента выбора цвета.

Мы видим две реализации объекта одного C# класса! Важно обратить внимание, что визуальный объект в красной рамке (сверху на двух картинках) и визуальный объект в черной рамке с желтым фоном (снизу) — это совершенно идентичные объекты одного C# класса (типа).

В этом примере демонстрируются возможности управления внешним видом специально созданного элемента выбора цвета. Цвет управляется‑настраивается с помощью ползунков для количества красной, зеленой и синей составляющих цвета с демонстрацией результирующего цвета в специальном сегменте внутри этого пользовательского контрола.

Пример явно демонстрирует разные аспекты компоновки, формы, внутреннего содержания элемента которыми можно управлять:

  • Форма сегмента отображения цвета изменилась с прямоугольной на круглую;

  • Расположение ползунков изменилось с горизонтального на вертикальное;

  • Были добавлены подписи для ползунков;

  • Цвет и фона, ширина и цвет рамки.

Каким же образом происходит такое изменение внешнего вида визуального элемента или как подменить описание внешнего вида элемента?

Дело в том, что в WPF реализована концепция разделения на два типа кода:

один из которых — C#‑код — реализует логику работы визуального элемента и/или его функциональность,

а второй — XAML‑код — реализует описание внешнего вида‑способ рисования визуального компонента,

причем это разделение достигается самым, наверно, радикальным способом — описание внешнего вида пишется на другом языке, на декларативном языке XAML. Для создания элементов, лишенных внешнего вида, такое описание внешнего вида включает в себя все визуальные составляющие, такие как:

  • цвета (фона, под‑элемента, …),

  • линии,

  • геометрические фигуры,

  • низкоуровневые визуальные элементы входящие в состав, такие как текст, рамки,

  • геометрия взаимного расположения элементов.

Далее это описание внешнего вида выносится в так называемый темплейт (template) — шаблон, который можно понимать как конфигурацию или даже сборку составляющих примитивных‑библиотечных визуальных элементов, которые формируют законченное сложное изображение контрола.

Создание особенной кастомной кнопки

Пусть нам нужно некоторое особенное оформление для кнопки как на рисунке:

        Кастомная кнопка в обычном состоянии                       Кастомная кнопка с наведенным курсором мыши
Кастомная кнопка в обычном состоянии Кастомная кнопка с наведенным курсором мыши

Тут надо отметить, что слово «создание» звучит, конечно, разочаровывающе, для такой, в общем то, рутинной работы, как создание кнопки, хоть она и трижды кастомная. Но дело в том, что для WPF это не совсем корректное слово в этом контексте. Мы же не собираемся менять логику работы кнопки — она также должна отрабатывать нажатие мышью и передавать соответствующее событие в код для обработки. В данном случае нам нужно только изменить внешний вид кнопки, и как мы уже отметили выше, для WPF это отдельная задача, которая никак не затрагивает исполняющий класс этого визуального объекта.

Чтобы обычная кнопка стала выглядеть по‑особенному, как мы видим на рисунке, надо следующим образом переопределить ее стандартный темплейт, просто перечислив вот таким образом те рисованные объекты, из которых она теперь будет состоять:

<ControlTemplate TargetType="Button">
    <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" ClipToBounds="True">
        <!-- Outer Rectangle with rounded corners. -->
        <Rectangle x:Name="outerRectangle" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="{TemplateBinding Background}" RadiusX="20" RadiusY="20" StrokeThickness="5" Fill="Transparent" />
        <!-- Inner Rectangle with rounded corners. -->
        <Rectangle x:Name="innerRectangle" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Transparent" StrokeThickness="20" Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20" />
        <!-- Present Content (text) of the button. -->
        <DockPanel Name="myContentPresenterDockPanel">
            <ContentPresenter x:Name="myContentPresenter" Margin="20" Content="{TemplateBinding  Content }" TextBlock.Foreground="Black" />
        </DockPanel>
    </Grid>
    <ControlTemplate.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
        <Setter Property="Background" Value="Red"/>
    </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Здесь мы видим, например, что приходится применять тригер, чтобы кнопка реагировала на попадание курсора мыши (событие IsMouseOver) и перерисовывалась с красным цветом фона (Background).

Где найти кнопку с пиктограммой и с текстом

На Хабре есть целая статья посвященная этой «проблеме» создания кастомизированной кнопки:

WPF: 4 варианта кнопки с иконкой и текстом

Суть в том, что WPF позволяет определить чуть ли не десятки способов добавления иконки‑пиктограммы на кнопку. Например, автор указанной статьи описывает шесть таких способов, достаточно сильно ограничивая себя в том, что считать способом добавления иконки на кнопку.

Вот так вот могут выглядеть кнопки «ОК» и «Cancel», созданные на WPF:

Что называется, найдите хоть одно отличие от кнопок из WinForms.

Конечно, визуально невозможно заметить, что, для эксперимента, я еще и изменил темплейт кнопки «Cancel», и она теперь сделана на основе библиотечного объекта StackPanel, тогда как исходный темплейт, использованный для кнопки «ОК», собран на основе тоже библиотечного объекта Grid.

Исходный темплейт для кнопки «ОК»:

<DataTemplate>
    <Grid>
    <Image
        Source="{Binding Path=(local:EyeCandy.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"
        HorizontalAlignment="Left"
        Margin="8,0,0,0"
        Height="16"
        Width="16" />
    <TextBlock
        Text="{TemplateBinding Content}"
        HorizontalAlignment="Center" />
    </Grid>
</DataTemplate>
Альтернативный темплейт для кнопки «Cancel»:
<DataTemplate>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="101">
        <Image Source="{Binding Path=(local:EyeCandy.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"
Height="16"
Width="16" HorizontalAlignment="Left" />
        <TextBlock Text="Cancel" HorizontalAlignment="Center" Margin="22,0,0,0"/>
    </StackPanel>
</DataTemplate>

Вы можете задать вопрос: зачем же нужно такое разнообразие, когда мне (нам) достаточно простой кнопки с иконкой? Ответ не заставит себя ждать: WPF спроектирован для того, чтобы снять ограничения на реализацию любого уникального пользовательского интерфейса, да(!) эти дополнительные возможности по кастомизации стандартных контролов и разработке новых делают разработку UI более сложной хотя бы потому, что теперь надо хоть немножко понимать эту новую концепцию с новым языком XAML. Да! Система WPF более сложная для понимания, но эта сложность позволяет получить практически неограниченные возможности для построения уникальных визуальных представлений. При этом код для более сложных решений будет значительно компактнее, чем код для такого же визуального представления, написанный в старой парадигме. Вот эти новые возможности и оптимизация более сложных, зачастую даже условно невозможных решений, в рамках старой парадигмы с лихвой окупают повышение порога вхождения в использование технологии WPF.

Комментарии (18)


  1. Vanirn
    00.00.0000 00:00

    Собственно, у нас не было большого выбора, поскольку проект реализуется под Windows, и большая часть кода выполнена на C#.

    Не знаю, когда запускался ваш проект, но если в прошлом году, то почему не рассматривали MAUI?

    PS: По моему мнению для джуна и мидла (не так давно пришедшего в айти, и пропустившего всякие там XML-конфиги и подобное), вот этот XAML выглядит как Китайская грамота, поэтому Flutter в купе с поставляемым из коробки UI-kit, без XAMLа и предлагающей строить представление через код, является куда более доступной технологией нежели чем, новомодный MAUI или тот же WPF, даже при всей моей любви к C# с .NET и отторжению React.


    1. rukhi7 Автор
      00.00.0000 00:00

      почему не рассматривали MAUI?

      так для него нужна 2022 студия как я понимаю, а у нас пока куплена и используется 2019.


      1. Vanirn
        00.00.0000 00:00

        Хм, но бесплатная версия VS Community 2022 так же поддерживает MAUI.
        Хотя я использую Rider по подписочной модели и подобных сложностей не возникает, разве что лаг с поддержкой нововведений в .NET примерно в месяц.


        1. rukhi7 Автор
          00.00.0000 00:00

          ну мне как то экспериментами заниматься не всегда есть время, или не всегда хочется, что ли.

          Надо ведь и работу работать.


    1. AgentFire
      00.00.0000 00:00
      +1

      Если есть аллергия на XAML, то всю его функциональность можно написать на C# (собственно, так оно и происходит при "компиляции" XAML).


      1. rukhi7 Автор
        00.00.0000 00:00
        +3

        всю функциональность XAML можно написать на C# (собственно, так оно и происходит при "компиляции" XAML)

        написать на C# вместо написать на XAML действительно можно , только писать придется намного больше, поэтому не рекомендуется так делать.

        А насчет того что

        так оно и происходит при "компиляции" XAML

        насколько я знаю С# компилируется в код, а XAML компилируется в бинарные данные, эти данные парсит и создает объекты по ним некоторая встроенная спец-функция фреймворка. Тут есть большая разница насколько я знаю.


        1. AgentFire
          00.00.0000 00:00

          Да, вы правы, а я ошибся.


      1. Vanirn
        00.00.0000 00:00

        Ну да, для MAUI есть даже специальный пакет позволяющий писать представление через код, но пакет этот community-driven и официально не поддерживается MS.


    1. rukhi7 Автор
      00.00.0000 00:00
      +1

      вот этот XAML выглядит как Китайская грамота

      сравнение не совсем корректное, на китайском языке разговаривает где то 1/5 населения планеты, соответственно 1/5 населения планеты считает китайскую грамоту вполне читаемой! WPF-грамоту вряд ли поймет 1/1000 населения планеты даже со словарем :) !

      замечание исключительно чтобы поднять настроение :) !


  1. euroUK
    00.00.0000 00:00
    +1

     "новомодным" и "WPF"

    Непонятно, что эти два слова делают вместе. Я еше новомодный Silverlight знал.

    Хотя все это все равно лучше, чем то что есть сейчас в вебе


    1. rukhi7 Автор
      00.00.0000 00:00

      Это исключительно по моим ощущениям! Мне кажется, очень многие до сих пор так воспринимают эту технологию несмотря на долгие годы ее существования. Мне например, при внутреннем обсуждении статьи, предлагали включить некую вводную про WPF, а то приходится гуглить что это такое.


    1. AgentFire
      00.00.0000 00:00
      +4

      Но всё же WPF незаслуженно непопулярен.


    1. a-tk
      00.00.0000 00:00

      Просто в наше время десктоп большинству видится чем-то рудиментарным. Совершенно зря, конечно, но, объективно, многие сервисы сейчас действительно уехали в веб...


      1. euroUK
        00.00.0000 00:00
        +2

        Абсолютно зря.

        Есть немало случаев, когда любое десктопное приложение работает в разы лучше и эффективнее, чем веб. Работа с данными - это одна из тех вещей. Как бы там реакты и ангуляры не развивались, но таблицы на сотни тысяч строк с возможностью группировки и фильтрации на лету, которые на десктопе летали еще в 2008, в вебе до сих пор работают или плохо, или требуют кучу кода.

        Верстка чего-то непростого - аналогично, огромная проблема в вебе, в отличии от XAML.

        Однако, мы живем в мире победивших эффективных менеджеров, которые в компаниях на десятки тысяч человек не могут найти пару людей на поддержку аж трех! платформ и мы получаем скайп на электроне. Который тормознутее, глючнее и наверняка требует больше людей на разработку.


  1. agranom555
    00.00.0000 00:00

    Посмотрите в сторону Avalonia. Улучшенный wpf и кросс платформенный при этом


    1. rukhi7 Автор
      00.00.0000 00:00

      Какой смысл в сторону смотреть когда и так все работает?


      1. a-tk
        00.00.0000 00:00

        Во-первых, AvaloniaUI кроссплатформенная, в отличие от прибитого гвоздями к Windows WPF.

        Во-вторых, в AvaloniaUI исправлено ряд родовых травм WPF, таких как убогая система стилей, нетипизированные свойства и многое другое.

        А ещё авторы Avalonia выкатили слой совместимости с WPF https://avaloniaui.net/XPF.


        1. rukhi7 Автор
          00.00.0000 00:00

          Может вы тогда сможете разъяснить что за бизнес-модель у этой Авалониии когда они пишут:

          Yes. Avalonia XPF comes with both documentation and support. Our engineers will take your apps and ensure they run perfectly before we handover to your team.

          Что бы это значило:

          "Наши инженеры возьмут ваши приложения и убедятся, что они работают идеально, прежде чем мы передадим их вашей команде."

          ???

          Вы предлогаете воспользоваться услугами инженеров Авалонии (видимо недорого) чтобы добиться кроссплатформенности, которая мне не нужна?

          А по поводу "убогая система стилей, нетипизированные свойства и многое другое " было бы интересно узнать что вы имеете ввиду на хоть одном демонстрирующем реальном примере.

          Всегда интересно узнать как можно делать лучше!