Sunday, December 8, 2013

Windows Forms vs WPF

Эта статья будет посвящена двум совершенно разным подходам к разработке прикладных программ на основании WPF (Windows Presentation Foundation) и Windows Forms (WinForms). Идея написания статьи возникла после наблюдения за начинающими разработчиками, которые решают, переходить им на WPF/XAML или нет. Основное их стремление изучать WPF основывается на том, что эта технология становится сейчас очень популярной. А язык декларативной разметки XAML (eXtensible Application Markup Language) позволяет писать код как для WPF, так и, например, к тому же Silverlight или для Windows Phone. Будучи ярым поклонником WPF и языка декларативной разметки XAML, в данном контексте не буду прибегать к субъективизму, расхваливая некую технологию, а постараюсь описать, в каких случаях применять тот или иной способ разработки. Почему-то многие разработчики после перехода с Windows Forms на WPF начинают критиковать Windows Forms, часто наблюдается и обратная реакция. Мне приходилось бывать в шкуре как первых, так и вторых.
В первую очередь, стоит охладить пыл новичков, которые, увидев возможности визуального интерфейса WPF, начинают максимально использовать его, не думая о последствиях. Во-первых, использовать WPF или Windows Form нужно исходя из решаемой задачи. Если у Вас простой интерфейс на парочку кнопочек и форм, то делайте задачу на том, на чем у Вас выйдет быстрее. В первую очередь анализируем то, что нужно реализовать, а затем делаем.
В этой статье, в отличие от предыдущих, код будет приводиться по минимуму, а в основном текстовое описание и рисунки для объяснения сути. Так как проверено на практике, что пользователи и разработчики лучше воспринимают графическую информацию, нежели программный код и текстовую информацию. Для примера отличия использования технологий WPF приведу пример создания кнопки со скругленными краями. Пример для Windows Forms взят с книги “Чарльз Петцольд, Программирование с использованием Microsoft Windows forms”. Для WPF пример взят с книги Adam Nathan, Windows Presentation Foundation Unleashed (WPF)
Пример круглой кнопки на Windows Forms:
namespace RoundButtonDemo
{
    //-------------------------------------------
    // RoundButton.cs (c) 2005 by Charles Petzold
    //-------------------------------------------
    class RoundButton : Button
    {
        public RoundButton()
        {
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        }
        public override Size GetPreferredSize(Size szProposed)
        {
            // Base size on text string to be displayed.
            var grfx = CreateGraphics();
            var szf = grfx.MeasureString(Text, Font);
            var iRadius = (int)Math.Sqrt(Math.Pow(szf.Width / 2, 2) +
                                         Math.Pow(szf.Height / 2, 2));
            return new Size(2 * iRadius, 2 * iRadius);
        }
        protected override void OnResize(EventArgs args)
        {
            base.OnResize(args);

            // Circular region makes button non-rectangular.
            var path = new GraphicsPath();
            path.AddEllipse(ClientRectangle);
            Region = new Region(path);
        }
        protected override void OnPaint(PaintEventArgs args)
        {
            var grfx = args.Graphics;
            grfx.SmoothingMode = SmoothingMode.AntiAlias;
            var rect = ClientRectangle;

            // Draw interior (darker if pressed).
            bool bPressed = Capture & ((MouseButtons & MouseButtons.Left) != 0) &
                ClientRectangle.Contains(PointToClient(MousePosition));

            var path = new GraphicsPath();
            path.AddEllipse(rect);
            var pgbr = new PathGradientBrush(path);
            int k = bPressed ? 2 : 1;
            pgbr.CenterPoint = new PointF(k * (rect.Left + rect.Right) / 3,
                                          k * (rect.Top + rect.Bottom) / 3);
            pgbr.CenterColor = bPressed ? Color.Blue : Color.White;
            pgbr.SurroundColors = new Color[] { Color.SkyBlue };
            grfx.FillRectangle(pgbr, rect);

            // Display border (thicker for default button)
            Brush br = new LinearGradientBrush(rect,
                            Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 128),
                            LinearGradientMode.ForwardDiagonal);
            var pn = new Pen(br, (IsDefault ? 4 : 2) * grfx.DpiX / 72);
            grfx.DrawEllipse(pn, rect);

            // Draw the text centered in the rectangle (grayed if disabled).
            var strfmt = new StringFormat();
            strfmt.Alignment = strfmt.LineAlignment = StringAlignment.Center;
            br = Enabled ? SystemBrushes.WindowText : SystemBrushes.GrayText;
            grfx.DrawString(Text, Font, br, rect, strfmt);

            // Draw dotted line around text if button has input focus.
            if (Focused)
            {
                var szf = grfx.MeasureString(Text, Font, PointF.Empty,
                                               StringFormat.GenericTypographic);
                pn = new Pen(ForeColor);
                pn.DashStyle = DashStyle.Dash;
                grfx.DrawRectangle(pn,
                    rect.Left + rect.Width / 2 - szf.Width / 2,
                    rect.Top + rect.Height / 2 - szf.Height / 2,
                    szf.Width, szf.Height);
            }
        }
    }
}
Как это выглядит на экране:
Пример круглой кнопки на WPF:
<Window x:Class="RoundButtonWpfDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <ControlTemplate x:Key="buttonTemplate" TargetType="{x:Type Button}">
                <Grid>
                    <Ellipse x:Name="outerCircle" Width="100" Height="100">
                        <Ellipse.Fill>
                            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                <GradientStop Offset="0" Color="Blue"/>
                                <GradientStop Offset="1" Color="Red"/>
                            </LinearGradientBrush>
                        </Ellipse.Fill>
                    </Ellipse>
                    <Ellipse Width="80" Height="80">
                        <Ellipse.Fill>
                            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                <GradientStop Offset="0" Color="White"/>
                                <GradientStop Offset="1" Color="Transparent"/>
                            </LinearGradientBrush>
                        </Ellipse.Fill>
                    </Ellipse>
                    <Viewbox>
                        <ContentControl Margin="30" Content="{TemplateBinding Content}"/>
                    </Viewbox>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="outerCircle" Property="Fill" Value="Orange"/>
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="RenderTransform">
                            <Setter.Value>
                                <ScaleTransform ScaleX=".9" ScaleY=".9"/>
                            </Setter.Value>
                        </Setter>
                        <Setter Property="RenderTransformOrigin" Value=".5,.5"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Grid.Resources>
       
        <Button Template="{StaticResource buttonTemplate}">OK</Button>
          
    </Grid>
</Window>
Результат:
Больше примеров кода не будет. Только рисунки и диаграммы. Для начала рассмотрим, в чем отличие этих двух подходов.
На рисунке изображены подходы с использованием WPF и Windows Forms. Описание этих двух подходов будет основываться на собственном опыте написания корпоративных приложений на основе паттернов MVP/MVVM. Если у Вас большой промышленный проект, написанный на WPF или Windows Forms, и вся логика написана во view и представляет собой несколько тысяч строк кода, подумайте о том, чтобы использовать паттерны MVP для Windows Forms и MVVM для WPF. Если Вы просто перейдете с таким подходом с Windows Forms на WPF, у Вас появится множество возможностей существенно подпортить репутацию проекта. На WPF написать плохой интерфейс и код намного легче, чем на Windows Forms. Поэтому еще раз настоятельно рекомендую: если Вы не знакомы с одним из этих паттернов, при программировании для используемой в данный момент технологии, то при переходе на другую технологию пробелы в знаниях и корявый код перенесутся на другую технологию. Приведу цитату Мартина Голдинга: «Всегда пишите код так, будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете». Итак, надеясь, что Вы осознанно подходите к выбору новой технологии, приступим к дальнейшему описанию технологий, описанных выше. Рассмотрим для начала подход для разработки прикладных приложений на Windows Forms.
Windows Forms
Подход к разработке прикладных программ на Windows Forms основывается на графическом интерфейсе GDI (Graphics Device Interface, Graphical Device Interface) - это интерфейс Windows для представления графических объектов и передачи их на устройства отображения, такие как мониторы и принтеры.
GDI отвечает за отрисовку линий и кривых, отображение шрифтов и обработку палитры. Он не отвечает за отрисовку окон, меню и т.п., эта задача закреплена за пользовательской подсистемой, располагающейся в user32.dll и основывающейся на GDI.
Одно из преимуществ Windows Forms - в том, что на нем можно писать коссплатформенные приложения. Простые проекты, написанные на Windows Forms, можно довольно легко перенести на другую операционную систему, если на ней установлен .Net Framework нужной модели, на котором написан Ваш проект. Выглядит это действительно впечатляюще, но на практике очень часто проект не будет работать. Особенно при использовании посторонних контролов, написанных другой компанией. Например, DevExpress, Telerik, MailBee и т.д. С вероятностью 90% Ваш проект не заработает с первого раза. Возможно, у Вас получится со временем привести проект к нужному виду. Но это будет стоить очень много времени. Поэтому если вдруг Вы решили все-таки писать кроссплатформенное приложение на С#, посмотрите в сторону Mono, Gtk или новый фреймворк, который сейчас становится популярным - Xamarin(для мобильных приложений). Поэтому лучше остановить выбор на QT/C++ или Java. Но если Вы не знакомы с этими языками, вам останется выбрать один из вариантов, предложенных мной выше, либо использовать какой-то другой известный Вам подход.

Рассмотрим паттерн Model-View-Presenter (MVP), который часто используют для Windows Forms. MVP - шаблон проектирования пользовательского интерфейса, который был разработан для облегчения автоматического модульного тестирования и улучшения разделения ответственности в презентационной логике (отделения логики от отображения):
  • Модель (Model) представляет собой интерфейс, определяющий данные для отображения или участвующие в пользовательском интерфейсе иным образом.
  • Представление (View) - это интерфейс, который отображает данные (Модель) и маршрутизирует пользовательские команды (или события) Presenter-у, чтобы тот действовал над этими данными.
  • Presenter действует над Моделью и Представлением. Он извлекает данные из хранилища (Модели) и форматирует их для отображения в Представлении.

 На схемее ниже показано графическое представление MVP.
На данный момент контролы для Windows Forms по количеству превосходят WPF контролы в несколько раз. Это связано, в первую очередь, с тем, что Windows Forms появился в .Net Framework 2.0 , а WPF - в .Net Framework 3.0. Разрабатывать приложения на Windows Forms очень просто. Для визуального интерфейса в основном большая часть логики связана с перетаскиванием нужных контролов на форму.

WPF
Подход к разработке прикладных программ на Windows Presentation Foundation (WPF) основан на DirectX. Весь код транслируется в код для трансляции в DirectX с помощью библиотеки milcore.dll. В основе WPF лежит векторная система визуализации, не зависящая от разрешения устройства вывода и созданная с учётом возможностей современного графического оборудования. WPF предоставляет средства для создания визуального интерфейса, включая язык XAML (Extensible Application Markup Language), элементы управления, привязку данных, макеты, двухмерную и трёхмерную графику, анимацию, стили, шаблоны, документы, текст, мультимедиа и оформление.

Графической технологией, лежащей в основе WPF, является DirectX, в отличие от Windows Forms, где используется GDI/GDI+. Производительность WPF выше, чем у GDI+, за счёт использования аппаратного ускорения графики через DirectX. С помощью XAML мы можем создавать визуальное представление (view), по сути, на xml - подобном языке с мощными возможностями. Приложения на WPF не являются переносимыми и могут быть использованы только в операционной системе Windows. Для написания корпоративных решений используется паттерн MVVM (Model-View-ViewModel) предназначен для создания приложений для WPF/Silverlight. Основная особенность данного паттерна заключается в том, что весь код с View(представления) выносится в ViewModel (модель представления), а вся привязка осуществляется черед байндинг, прописанный в XAML-разметке. Для простоты работы с MVVM был разработан MVVM Toolkit , который включает шаблон для Visual Studio и позволяет использовать данный паттерн без особых усилий. Если Вы не знакомы с данным паттерном, рекомендую ознакомиться с моей статьей "Основы паттерна MVVM". Благодаря этому паттерну в приложении WPF можно разделить реализацию интерфейса и логики. Реализаций интерфейса могут заниматься дизайнеры на инструменте Expression Blend (либо во встроенном редакторе студии), а программисты будут заниматься исключительно логикой работы программы. Как выглядит представление паттерна MVVM, можно увидеть ниже на рисунке.
WPF предоставляет мощную систему байндинга, которая намного мощнее используемой в Windows Forms. Сейчас пришла пора объяснить код с овальной кнопкой, приведенный в начале статьи. В отличие от Windows Forms, для того, чтобы сделать нестандартный интерфейс в каком либо существующем контроле, не нужно создавать новый контрол. Достаточно для этой кнопки сделать новый стиль и привязать его к данному контролу через свойство Template. Это намного удобнее, чем в WPF. Но в большинстве случаев не нужно перерисовывать внешний вид контрола, в таких случаях полностью подойдет Windows Forms. WPF имеет также преимущество перед Windows Forms в поддержке 2D/3объектов. Также WPF имеется мощный инструмент для работы с анимацией через XAML код. В Windows Forms простая анимация обычно требует объект таймер и отрисовки нужной информации по событию tick этого таймера. Еще одно достоинство - работа с аудио, видео и голосом.
Итоги

В данной статье я постарался описать сравнение двух подходов к разработке прикладного программного обеспечения, основываясь на подходе с использованием Windows Forms и WPF. При усилиях быть объективным к этим технологиям разработки, WPF в материале получилась все же более восхваленная, чемWindows Forms. Возможно, аргументом в пользу этого будет тот факт, что WPF имеет намного больше возможностей по написанию программ, чем Windows Forms. Прежде чем выбрать какую то технологию для разработки, нужно проанализировать, что Вам больше подходит по дизайну. Если в Вашем приложении простой дизайн, то WPF в этом случае, по сути, незачем. Также нужно учитывать скорость разработки продукта на первой и второй технологии. Если на Windows Forms это займет у Вас 3 дня, а наWPF - 7 дней, то не имеет смысла использовать WPF. То же самое касается паттернов MVP/MVVM: если у Вас приложение, которое занимает несколько дней для разработки, и если Вы неопытный разработчик, то применение данных паттернов только отсрочит написание проекта. У опытных разработчиков же либо есть наработки, либо они могут взять какой-то из MVVM-тулкитов для построения своего приложения.

No comments:

Post a Comment