Showing posts with label WindowsFroms. Show all posts
Showing posts with label WindowsFroms. Show all posts

Friday, February 7, 2014

Взаимодействие F# с Windows Forms и WPF

Здравствуйте, уважаемые читатели моего блога. Сегодня мы поговорим о взаимодействии F#-приложения с Windows Forms и WPF. В последнее время я активно занимаюсь изучением F# и предлагаю вам окунуться вместе со мной в возможности этого языка. Как говорится, меньше слов – больше дела. Для начала создадим проект с названием TestFSharp для примера. Как это сделать, можно увидеть на рисунке ниже.
Затем нужно удалить точку входа в нашу программу, которая по умолчанию будет у Вас создана.
[<EntryPoint>]
let main argv =
    printfn "%A" argv
    0 // return an integer exit code
Эта точка входа обозначает, что Ваше приложение будет запущено на Console Application. Нам же нужно будет запустить наше приложение как приложение Windows Application. Для этого переходим в окно настроек нашего проекта и изменяем запускаемый проект с на Console Application на Windows Application, как показано на рисунке ниже.
А сейчас немножко "поколдуем" над нашим кодом. Для того, чтобы наши контролы заработали, нужно добавить ссылки на System.Windows.Forms и
System.Drawing.
После того, как мы всё сделали, можно спокойно перейти к реализации. В первую очередь необходимо указать пространства имен, которые мы будем использовать. Для этого добавим в наш проект таки строки:
open System
open System.Windows.Forms
open System.Drawing
Мы говорим нашей исполняющей среде, что будем использовать контролы и классы с данного пространства имен. Давайте для примера создадим простенькую форму, в которую добавим кнопочку, при нажатии на которую выведем сообщение через MessageBox.Show(). Полный исходный код приведен ниже.
open System
open System.Windows.Forms
open System.Drawing

let form = new Form(Text = "F# Program")
let button = new Button(Text = "Button Click")
button.Location <- new Point( 100, 100)
form.Controls.Add(button)

button.Click.Add(fun evArgs -> MessageBox.Show("Click me") |> ignore)

Application.Run(form)
С помощью строк
let form = new Form(Text = "F# Program")
let button = new Button(Text = "Button Click")
создаем форму, затем создаем кнопку и добавляем ее на форму. Затем чтобы наш контрол не выводился с верхнего левого края, зададим ему отступ справа и слева на 100 пикселей.
button.Location <- new Point( 100, 100)
Это делается с помощью оператора <-. Этот оператор позволяет изменять только свойства, помеченные как mutable. По умолчанию свойства в F# помечены как неизменяемые, поэтому помните, когда будете писать код, что для свойств нужно указывать явно, что они являются изменяемыми. Строка
form.Controls.Add(button)
добавляет созданную кнопку на форму.
button.Click.Add(fun evArgs -> MessageBox.Show("Click me") |> ignore)
Код, приведенный выше, создает обработчик события нажатия на кнопку. Этот обработчик создан с помощью лямбда-выражения
fun evArgs -> MessageBox.Show("Click me")
Поскольку нам не важен результат, который возвращает метод MessageBox.Show, мы указываем игнорировать данный результат с помощью строки
|> ignore
После нажатия на клавишу F5 видим результат, подобный рисунку ниже.

F# и WPF
Приведем еще один пример использования F# совместно с WPF. Поскольку пример с созданием контролов мы уже видели в предыдущем примере, рассмотрим вариант с использованием XAML-файла. Вы не сможете создать XAML-файл в самой среде Visual Studio 2012, поскольку эти типы файлов недоступны для проекта на языке F#.
Нам необходимо предпринять маленькую хитрость и создать XAML-окно в отдельном редакторе, например, в той же студии или в Expression Blend, а затем добавить его в наш проект. Признаюсь, что для реального проекта это не очень удобно. Но у нас тестовый пример, и сведения о том, как запустить WPF-приложение с F#, думаю, будут интересными. Для работы с нашим WPF-приложением необходимо добавить следующие библиотеки в проект:

WindowsBase.dll
PresentationCore.dll
PresentationFramework.dll
System.Xaml.dll
Пример показан на рисунке ниже.
Простой пример без использования XAML-файла можно написать так:
open System
open System.Windows


let win = new Window(Title = "Hello WPF")
win.Show() |> ignore

#if COMPILED
[<STAThread()>]
do
    let app =  new Application()
    app.Run() |> ignore
#endif
Но похожий пример мы уже создавали, когда написали Windows Forms-приложение в начале статьи. Поэтому пойдем более сложным путем. Выберем созданный уже заранее приготовленный шаблон и добавим его в студию, нажав на проекте правой клавишей мыши и выбрав пункт меню Add->Existing Item…
Выбираем All Files (*.*) и добавляем наше окно, которое хотим отобразить на экране.
Вот какой вид имеет наше окно:
<Window x:Class="WpfApplicationTestFSharpProject.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>
        <StackPanel Orientation="Vertical">
            <TextBox x:Name="FSharpBook" Text="Test"></TextBox>
            <Button x:Name="PressButton" Content="Click Me"></Button>
        </StackPanel>
    </Grid>
</Window>
Нам необходимо для прикрепленного окна MainWindow.xaml установить в окне Properties свойство Build Action в Resources. После того, как мы проделали необходимую работу, посмотрим, как будет выглядеть наше решение.
open System
open System.Windows
open System.Windows.Controls

[<STAThread>]
[<EntryPoint>]
let main(_) =
  let window = Application.LoadComponent(new Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
  let button = window.FindName("PressButton") :?> Button
  let textBox = window.FindName("FSharpBook") :?> TextBox
  textBox.Text <- "Hello F#"
  button.Click.Add(fun _ ->
    textBox.Text <- textBox.Text.ToUpper())

  window.Show() |> ignore

  let application = new Application()
  application.Run()
Для нашей программы укажем точку входа и то, что наша программа должна запускаться в основном потоке. Это делается с помощью строк
[<STAThread>]
[<EntryPoint>]
let main(_) =
Строка _ как параметр функции main указывает на то, что нам не важно, какие параметры будут переданы в функцию, так как функция принимает параметры командной строки, мы можем указать args как аргумент функции. Следующим шагом мы загружаем наше окно с помощью функции
let window = Application.LoadComponent(new Uri("MainWindow.xaml", UriKind.Relative))
и приводим результат, который возвращает нашу функцию к типу Window (Примечание: функция Application.LoadComponent возвращает Object). Операция приведения в F# очень простая и легко запоминается:

:?>

Следующим этапом находим наши контролы: TextBox и Button по имени.
let button = window.FindName("PressButton") :?> Button
let textBox = window.FindName("FSharpBook") :?> TextBox
Для примера изменим текст, который был в TextBox, и напишем новый текст "Hello F#". Как это сделать, посмотрите ниже:
textBox.Text <- "Hello F#"
Для кнопки мы создадим обработчик события с помощью лямбда-выражения по такому же принципу, как делали для примера Windows Forms. Только для данного  примера мы в обработчике изменим текст в TextBox, который нашли выше, на текст верхнего регистра.
button.Click.Add(fun _ ->
textBox.Text <- textBox.Text.ToUpper())
Осталось каких-то "два штриха". Нужно показать наше созданное окно и запустить основное приложение с помощью метода application.Run()

Итоги
В данной статье мы рассмотрели возможности взаимодействия F#-проекта с Windows Forms и WPF-контролами. Надеюсь, Вам понравился экскурс в мир F#. Жду ваши комментарии и с удовольствием на них отвечу.   

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/3D объектов. Также WPF имеется мощный инструмент для работы с анимацией через XAML код. В Windows Forms простая анимация обычно требует объект таймер и отрисовки нужной информации по событию tick этого таймера. Еще одно достоинство - работа с аудио, видео и голосом.
Итоги

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