На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: maxim84_, juice
  
> Введение в WPF, Анатомия Application
    Целью, ряда планируемых статей, которые надеюсь, будут представлены Вашему вниманию, является изучение WPF , но не в буквальном понимании этой фразы, их целью является попытка осветить собственные поиски и изыскания на пути изучения тех или иных составляющих, новой библиотеки от Microsoft. На мое твердое убеждение, достичь успеха при изучении какой-либо новой технологии позволяет комплексный подход, который состоит из двух принципов. Первый и самый главный принцип - практика. Только проектируя и разрабатывая настоящие приложения можно добиться прогресса и почерпнуть практические знания. Вторая важная составляющая это всестороннее исследование изучаемого предмета. Именно на исследовании и теоретических аспектах я и попытаюсь сконцентрировать свое внимание в своих обзорах библиотеки. Разве не интересно залезть под капот этому новому «заморскому» чудищу?! К тому же второй принцип позволяет подвести под практическую основу отличную теоретическую базу. Кто-то спросит, а зачем? И окажется по-своему прав. Ну, хотя бы для того, что бы иногда правильно отвечать на вопросы ньюбов на форуме sources.ru. Это, конечно же, была шутка. Основной лейтмотив, - без теории никогда не бывает практики. Любой попытке написать код с использованием не знакомой технологии или библиотеки, предшествует хотя бы беглый просмотр типов данных которые она предоставляет. Иногда предшествует чтение статей из серии “Getting Started”. Самое интересное, что читают обычно все и матерые профессионалы, и зеленые новички.

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

    Первое, что мы с Вами сделаем, - создадим, пустой проект, используя среду MS Visual Studio 2008. Для этого в меню File среды разработки выбираем пункт New – Project переходим в раздел C# - Windows и выбираем шаблон Empty Project. Даем ему имя HelloWPF, при этом не забываем указать, что собираемся использовать Framework 3.5
    Давайте добавим в наш проект файл с именем HelloWPF, для этого нам нужно в Solution explorer выбрать наш проект и в контекстном меню выбрать Add – New Item, после чего указать, что добавлять мы будем файл с исходным кодом (Сode File).

    Пока файл пуст. Что требуется для того, что бы превратить его в программу? Правильно класс, содержащий статический метод Main, который является отправной точкой, для любой программы, написанной на языке C#. Поторопимся и мы с Вами, добавив следующий код:

    ExpandedWrap disabled
      using System;
       
      namespace Juice.HelloWPF
      {
          public class HeloWPF
          {
              public static void Main()
              {
       
              }
          }
      }


    Можно скомпилировать код и выполнить его (Ctrl – F5), на экране мы увидим стандартную консоль.

    Что же нам потребуется, что бы превратит консоль в полноценное оконное WPF приложение? Совсем немного, немного - терпения. Прежде всего давайте выясним, какие сборки нам с Вами необходимы для разработки WPF программ? Основной функционал содержится в трех сборках это: PresentationCore, PresentationFramework и WindowsBase. Так же джентльменский набор потребует хорошо известной всем нам сборки System. Добавим их к проекту. В Solution Explorer, выбираем Reference – Add Reference, выделяем их в появившимся списке и жмем кнопку Ok. Теперь нам с Вами доступны все необходимые для корректной работы приложения типы. Какие же из них нам понадобятся для написания наипростейшего приложения? Прежде всего, это тип Application находящийся в namespace System.Windows сборки PresentationFramework, данный класс представляет собой абстракцию нашего приложения. Он обязательно необходим нашему приложению, что бы оно могло обрабатывать сообщения, поступающие от операционной системы. Это могут быть события, которые генерируются в ответ на действия пользователя или события, поступающие от устройств ввода-вывода и.т.д. Позже мы узнаем, что класс Application предоставляет нам с вами множество дополнительных возможностей, которые позволят нам более полно контролировать выполнение нашей программы. Приложение должно иметь один и только один объект Application. Давайте добавим его в наш код.

    ExpandedWrap disabled
      using System;
      using System.Windows;
       
      namespace Juice.HelloWPF
      {
          public class HeloWPF
          {
              public static void Main()
              {
                  Application app = new Application();
              }
          }
      }


    После компиляции программы мы увидим уже знакомую нам с Вами консоль. Для создания полноценного оконного приложения нам с Вами не хватает – окна! Класс представляющий в WPF окно, находится по уже знакомому нам с Вами адресу, в namespace System.Windows сборки PresentationFramework и называется он, как это не удивительно Window. Давайте, скорее добавим и его в нашу программу:

    ExpandedWrap disabled
      using System;
      using System.Windows;
       
      namespace Juice.HelloWPF
      {
          public class HeloWPF
          {
              public static void Main()
              {
                  Application app = new Application();
                  Window window = new Window();
              }
          }
      }


    Пробуем выполнить наше приложение … Упс. Что то пошло не так, мы получили сообщение об ошибке The calling thread must be STA, because many UI components require this. И это произошло при попытке инициализации объекта Window.

    Дело не хитрое и легко поправимое, путем добавления атрибута STAThread методу Main. Этим мы устанавливаем исходную модель потоков для основного потока приложения при взаимодействии с COM подсистемой. Стоит заметить, что наличие этого атрибута обязательно для любой WPF программы. Особо любопытных могу отправить по следующему адресу http://blogs.msdn.com/larryosterman/archive/2004/04/28/122240.aspx где очень и очень подробно описано значение STAThread в COM модели и не только это. Меня же заинтересовала, возможность посмотреть стектрейс. Также стало интересно, какое именно место привело к выбросу исключения, в нашей программе. Стектрейс может много нам рассказать о происходящем в момент инстанцирования экземпляра окна и мне бы хотелось узнать об этом также больше. Рассмотрим стектрейс исключения подробней, часть текста которого приведен ниже:

    at System.Windows.Input.InputManager..ctor()
    at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
    at System.Windows.Input.InputManager.get_Current()
    at System.Windows.Input.KeyboardNavigation..ctor()
    at System.Windows.FrameworkElement.EnsureFrameworkServices()
    at System.Windows.FrameworkElement..ctor()
    at System.Windows.Controls.Control..ctor()
    at System.Windows.Window..ctor()

    Мы видим, что вызов конструктора Window, привел к вызову цепочки конструкторов базовых классов в его иерархии: Control и FrameworkElement. Здесь следует уточнить, что Window наследует непосредственно классу ContentControl, но о нем, вероятно, мы поговорим в следующих обзорах.

    Для полноты картины представлю дерево наследования для класса Window:

    Object
    DispatcherObject
    DependencyObject
    Visual
    UIElement
    FrameworkElement
    Control
    ContentControl
    Window

    В конструкторе FrameworkElement , как мы видим дальше, был произведен вызов закрытого статического метода EnsureFrameworkServices.

    Цитата
    at System.Windows.FrameworkElement.EnsureFrameworkServices()


    который, судя по своему названию, гарантирует поддержку служб библиотеки для экземпляров FameworkElement. Что за службы такие? На самом деле все гораздо более прозаично. FrameworkElement содержит закрытую статическую переменную, относящуюся к типу FrameworkServise. Этот тип обеспечивает навигацию по элементам управления с использованием клавиатуры. Второй обязаностью класса FrameworkServise есть обеспечение поддержки для тултипов. Для этого он создает экземпляр класса PopupControlService. Просмотрев стектрейс еще выше, мы как раз и увидим, что в методе EnsureFrameworkServices происходит инициализация переменной типа KeyboardNavigation.

    Цитата
    at System.Windows.Input.KeyboardNavigation..ctor()


    KeyboardNavigation объявлен как внутренняя (internal) переменная FrameworkServise. На этот класс библиотекой WPF возлагается обязанность по обработке навигационных клавиш, а также клавиатурных сокращений связанных с навигацией. Сам же FrameworkElement, предоставляет статическое свойство с одноименным названием KeyboardNavigation, посредством которого наследники получают к нему доступ. Для выполнения своих обязанностей KeyboardNavigation использует класс InputManager. Ссылку, на который и пытается получить в своем конструкторе.

    Цитата

    at System.Windows.Input.InputManager.get_Current()


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

    Цитата
    at System.Windows.Input.InputManager..ctor()


    Посредсвом InputManager приложение (и даже мы ;) можем получить широкий доступ к вводимым пользователем данным. Именно так и поступает KeyboardNavigation. Приложение может получить ссылку на единственный экземпляр InputManager. Класс InputManager реализован как Singleton. Конструктор этого класса, в соответствии с шаблоном объявлен как приватный. Если Вы, захотите воспользоваться этим классом в вашей собственной программе, то его следует инстанцировать следующим образом:

    ExpandedWrap disabled
       InputManager manager = InputManager.Current;


    И так, что мы имеем? Исключение было сгенерировано при инициализации переменной типа InputManager. Именно в ее конструкторе находится условный блок, который проверяет текущую модель потоков COM на соответствие STAThread. Причем делается это посредством обычного вызова метода GetApartmentState для текущего исполняемого потока приложения и последующим сравнением полученного значения с значением STA перечисления ApartmentState. После успешной проверки, на «наличие атрибута» , InputManager подключает устройства ввода, которые может обработать подсистема ввода WPF, такие как клавиатура, мышь и стилус, в противном же случае бросает уже знакомое нам исключение. Ну, что двинемся дальше? Ведь теперь мы, по крайней мере, «лучше знаем», что делает WPF, создавая окно.

    Метод Run имеет перегруженную версию, которая принимает парамметром объект Window, благодаря чему нашу программу можно написать на строчку короче.

    ExpandedWrap disabled
      using System;
      using System.Windows;
       
      namespace Juice.HelloWPF
      {
          public class HeloWPF
          {
              [STAThread]
              public static void Main()
              {
                  Window window = new Window();            
                  Application app = new Application();
                  app.Run(window);
              }
          }
      }


    Результат идентичен.

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

    ExpandedWrap disabled
      using System;
      using System.Windows;
       
      namespace Juice.HelloWPF
      {
          public class HeloWPF
          {
              [STAThread]
              public static void Main()
              {
                  Window window = new Window();
                  window.Title = "First";
       
       
                  Window windowSecond = new Window();
                  windowSecond.Title = "Second";
                  windowSecond.Show();
       
                  Application app = new Application();
                  app.Run(window);
              }
          }
      }


    Результатом программы является вывод обеих окон, после закрытия окна First приложение завершает свою работу. В тоже время закрытие окна Second не приводит к такому результату. Почему? Дело в том, что Application считает одно из окон главным. По умолчанию это окно передаваемое Application в качестве парамметра и обычно это будет первое создаваемое приложением окно. Можем ли мы вообще повлиять на этот процесс? Можно ли назначить приложению другое главное окно? Оказывается да. У класса приложения есть волшебное свойство которое мы с Вами, можем установить, ShutdownMode. Оно может принимать одно из трех значений: ShutdownMode.OnLastWindowClose, ShutdownMode.OnMainWindowClose и ShutdownMode.OnExplicitShutdown. Установка первого говорит приложению, что методу Run стоит выполнять работу пока существует хотя бы одно окно которое он может обслуживать. Второе значение говорит методу Run, что ему следует завершить работу когда пользователь закроет главное окно. Последнее из указанных значений, говорит о том, что приложение может завершить свою работу только явно посредством вызова метода Shutdown() класса Application. Давайте разберемся в том, что происходит при вызове следующей строчки кода:

    ExpandedWrap disabled
      app.Run(window);



    Первое, что делает метод Run проверяет выполняется ли он в нужном потоке, далее следует проверка на то, что приложение выполняется не в броузере. У класса Application имется коллекция объектов Windows в которой метод Run ищет передаваемое ему окно и если не находит добавляет его в коллекцию. Далее следует проверка на наличие у приложения главного окна и если такое не установлено, то метод Run делает полученное окно – главным. В дальнейшем проводится проверка является ли окно видимым и если нет вызывается метод Show окна. После чего запускается цикл обработки сообщений. И так мы узнали о двух интересных свойствах объекта Application это Windows, которое хранит коллекцию окон приложения и MainWindow которое возвращает ссылку на главное окно приложения. При этом ни, что нам не мешает присвоить свойству MainWindow другое значение. Мы можем сделать главным любое другое окно по нашему усмотрению. Оба этих свойства можно без проблем задействовать в собственной программе. Если посмотреть на методы и события класса Application можно найти еще несколько интерессных событий и свойств:
    Startup – событие которое позволяет отследить начло работы нашего приложения, здесь удобно инициализировать данные уровня приложения.

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

    SessionEnding – событие которое позволяет нам отследить завершение сеанса работы пользователя в Windows или выключение компьютера.

    Activated – событие которое отрабатывает когда пользователь переключается на работу с одним из окон нашего приложения

    Diactivated – отрабатывает когда приложение становится неактивным, в результате того, что пользователь переключился на работу с другим приложением.

    DispatcherUnhandledException – срабатывает при возникновении в приложении необработанного исключения.

    Из свойств хочеться вделить Properties который является ассоциативным контейнером (хеш-таблицей), который содержит пары ключ-значение, где мы можем сохранять собственные данные уровня приложения. Резонен вопрос, как получить к этой коллекции из кода в приложении? Все очень просто. Я уже говорил, что класс Application может быть только один в программе и получить ссылку на него можно используя статическое свойство класса Application, Сurrent.

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

    Надеюсь каждый узнал для себя сегодня, что то новое. До новых встреч.
    Сообщение отредактировано: juice -
    Ни один победитель не верит в случайность.
      Рейтинг свой поднять хочешь?)
        Цитата ZOXEXIVO @
        Рейтинг свой поднять хочешь?)

        Так сложилось, что с понедельника по пятницу я разработчик, а по субботам педагог :) Иногда, выходит так, что не чем занять себя в воскресенье :) (шутка, про воскресенье шутка) Некоторые люди пишут стихи, считай это аналогичной потребностью.
        Ни один победитель не верит в случайность.
          Вот щас натренруешься на нас - потом издаваться будешь.
          Будешь издавать книжки малыми тиражами и все будут за ними охотиться...
          Станешь вторым Рихтером ;)
          ... А сегодня что для завтра сделал я?

          Brainbench Transcript ID#: 7848137
            Цитата Alexus @
            Будешь издавать книжки малыми тиражами и все будут за ними охотиться...
            Станешь вторым Рихтером

            Цитата Alexus @
            Вот щас натренруешься на нас - потом издаваться будешь.

            Ну кто бы говорил... :))))
            БольшимиAlexus, большими :)
            Ни один победитель не верит в случайность.
              Вопросо по данной теме. Читал вроде внимательно, но не понял.

              Почему при закрытии win1 у меня закроется все приложение, а при закрытии win2 только win2. Хотя я явно указал что win2 - главное окно.

              ExpandedWrap disabled
                            Window win1 = new Window();
                            win1.Title = "Fisrt window";
                 
                            Window win2 = new Window();
                            win2.Title = "Second window";
                            win2.Show();
                 
                            Application app = new Application();
                            app.MainWindow = win2;
                 
                            app.Run(win1);
                Цитата John-Desperado @
                Почему при закрытии win1 у меня закроется все приложение, а при закрытии win2 только win2. Хотя я явно указал что win2 - главное окно.

                Потому, что твое окно win2 живет самостоятельной жизнью вне нормального жизненного цикла приложения. В коллекции Windows у Application имеется одно единственное окно, которое win1, т.к дефолтная стратегия предпологает закрытие приложение по принципу "после уничтожения последнего". Приложение закрывается именно после закрытия win1. Что бы убедиться в том, что окну не нужен Application для того, что бы себе отобразаить достаточно удалить все упоминания Application, а в конце метода поставить Thread.Sleep(...);
                Ни один победитель не верит в случайность.
                  Добрый день.
                  Полезная статья, по себе знаю.
                  Когда, люди начинают, програмировать UI в какой нибудь IDE, достигают какого-то хорошего уровня
                  создания приложений.
                  После чего начинают задавать такие вопросы, "Как динамически можно создать кнопки на форме?".
                  ДА, знать всего нельзя, но спуститься в низ, иногда бывает полезно, подругому начинаешь смотреть на вещи.

                  Спасибо.
                    thanks
                    некоторые моменты стали более ясны

                    Цитата
                    ДА, знать всего нельзя, но спуститься в низ, иногда бывает полезно, подругому начинаешь смотреть на вещи.

                    ну... после Win32 API это совсем не кажется низами...
                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                    0 пользователей:


                    Рейтинг@Mail.ru
                    [ Script Execution time: 0,1182 ]   [ 17 queries used ]   [ Generated: 25.09.17, 20:47 GMT ]