Главная » WPF » Объект Application WPF

0

Объект Application  отвечает за управление  временем жизни приложения, отс# леживает видимые окна, освобождает ресурсы и контролирует глобальное состо# яние приложения. Логически WPF#приложение начинается  с вызова метода Run объекта Application.

using System;

using System.Windows;

namespace EssentialWPF {

static class Program { [STAThread]

static void Main() {

Application app = new Application();

Window w = new Window(); w.Title = «Hello World»; w.Show();

app.Run();

}

}

}

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

4 Маркер [STAThread] перед точкой входа обязателен. Как и в Windows Forms, а еще раньше в User32, WPF требует, чтобы поток пользовательского интерфейса находился в однопоточном апартаменте (STA), поскольку многие компоненты, которыми пользуется WPF (например, Clipboard) не могут ра) ботать иначе. Единообразия ради команда WPF решила сделать требование к STA непререкаемым.

компонентам  WPF#приложения5. Он будет работать до тех пор, пока приложение не завершится. В каждый момент времени может быть активен только один объект Application  (на самом деле, после начала работы приложения второй такой объект даже создать не получится). С помощью статического свойства Application.Current можно в любом месте программы получить доступ к текущему объекту Application.

Определение

Для  инкапсуляции логики  запуска  WPF#приложения обычно создают подк#

ласс класса Application:

// program.cs using System;

using System.Windows;

namespace EssentialWPF {

static class Program { [STAThread]

static void Main() {

MyApp app = new MyApp();

app.Run();

}

}

class MyApp : Application {

public MyApp() {

Window w = new Window(); w.Title = ”Hello World”; w.Show();

}

}

}

Одна из целей WPF состоит в том, чтобы программист писал как можно мень# ше стандартного  кода. Другая цель – всюду, где только можно, разрешить  декла# ративное программирование. В любом WPF#приложении приходится  писать ка# кой#то код в функции Main: задавать атрибут STAThread, создавать объект Application  и вызывать метод Run. Но WPF не требует, чтобы мы писали его сно# ва и снова в каждой программе, а позволяет  определить  приложение в виде фай# ла разметки.  Напоследок добавим два новых файла в файл проекта:

<!— myapp.xaml —>

<Application x:Class=’EssentialWPF.MyApp’ … />

// myapp.xaml.cs using System;

using System.Windows;

5  Точнее говоря, Run запускает диспетчер, который отвечает за отправку событий и сообщений. Подробнее диспетчер рассматривается в приложении.

namespace EssentialWPF {

partial class MyApp : Application {

public MyApp() {

Window w = new Window(); w.Title = ”Hello World”; w.Show();

}

}

}

<!— sample.csproj —>

<Project DefaultTargets=’Build’ xmlns=’http://schemas.microsoft.com/developer/msbuild/2003’>

<ItemGroup>

<ApplicationDefinition Include=’myapp.xaml’ />

<Compile Include=’myapp.xaml.cs’ />

</ItemGroup>

</Project>

Система  построения  получает  на входе этот файл  и генерирует  стандартный код, который  мы ранее написали  вручную. Ниже  приведен  фрагмент  сгенериро# ванного кода:

namespace EssentialWPF {

/// <summary>

/// MyApp

/// </summary>

public partial class MyApp : System.Windows.Application {

/// <summary>

/// Application entry point

/// </summary> [System.STAThreadAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()] public static void Main() {

EssentialWPF.MyApp app = new EssentialWPF.MyApp();

app.Run();

}

}

}

Обратите  внимание,  что в сгенерированном коде написанная вручную функ#

ция Main подменена6.

6 Как мы увидим ниже, Application — единственный элемент разметки, для которого не требуется вы)

зывать метод InitializeComponent.

Время жизни

В последней строке функции Main производится обращение к методу Application.Run. Рано  или  поздно  этот метод должен  вернуть  управление,  иначе приложение будет работать вечно. Одна из основных задач объекта Application зак# лючается  в том, чтобы контролировать время  жизни  процесса. Конструирование этого объекта знаменует начало работы приложения, а возврат из метода Run – его завершение. Между этими двумя моментами протекает жизнь приложения. Все WPF#приложения устроены одинаково:

Конструируется объект Application. Вызывается его метод Run.

Возбуждается событие Application.Startup.

Пользовательский код конструирует один или несколько объектов Window. Вызывается метод Application.Shutdown.

Вызывается метод Application.Exit. Метод Run возвращает  управление.

Инициализировать приложение можно двумя способами: (1) в конструкторе объ# екта Application или (2) в обработчике события Startup. Последнее предпочтительнее, так как в этот момент внутренняя инициализация объекта Application уже завершена (скажем, внутри конструктора еще не установлено свойство Application.Current):

<!— myapp.xaml —>

<Application x:Class=’EssentialWPF.MyApp’

Startup=’MyApp_Startup’

/>

// myapp.xaml.cs using System;

using System.Windows;

namespace EssentialWPF {

partial class MyApp : Application {

public MyApp() {

}

void MyApp_Startup(object sender, StartupEventArgs e) {

Window w = new Window(); w.Title = ”Hello World”; w.Show();

}

}

}

В какой#то момент метод Run должен вернуть управление, а для этого необхо# димо вызвать метод Application.Shutdown. Однако можно организовать програм# му так, чтобы метод Shutdown вызывался автоматически, и скоро – когда мы бу# дем говорить об объекте Window – я покажу, как это сделать. Возможно,  вы об#

ратили внимание, что в описании жизненного цикла приложения кое#что пропу# щено. Как говорится, «день у каждого бывает, когда дождь не затихает»*. Как пра# вило, разработчик рано или поздно  сталкивается с проблемой – то ли потому что в программе  есть ошибка,  то ли из#за непредвиденных действий  пользователя или (не дай бог!) в результате  ошибки в самой платформе.  Поэтому  всегда нуж# но включать код для обработки возможных  ошибок.

Обработка ошибок

Обработка  ошибок и исключений в приложении – тема достаточно обширная для целой книги. Программист должен решить, что делать в случае возникнове# ния ошибки, и понимать философию, которой мы руководствовались при проек# тировании этого аспекта WPF. Были  приложены  все усилия  к тому, чтобы вос# станавливать нормальную  работу после возможно  большего числа типов исклю# чений. Тут, правда, есть опасность порочного круга, поскольку, говоря о возмож# ности  восстановления, мы тем самым  ограничиваем  типы  исключений,  против которых вооружены!  Проблема  в том, что существуют  такие исключения, после которых восстановление невозможно. Самые очевидные примеры: StackOverflow Exception, OutOfMemoryException и ThreadAbortException.

Поток аварийно  завершается лишь в том случае, когда приложение вызывает ме# тод Thread.Abort. Если этого не делать, то исключение ThreadAbortException никогда и не возникнет. Переполнение стека чаще всего свидетельствует о бесконечной рекур# сии, хорошего в этом мало. Самым интересным из трех, наверное, является исключе# ние OutOfMemoryException. Когда системе не хватает памяти  – по#настоящему  не хватает, – может оказаться, что CLR не в состоянии выделить больше ни единого бай# та7. В таком случае невозможна своевременная компиляция, нельзя  выполнить ни упаковку переменных, ни получить память для новых объектов. Если не считать этих трех особых случаев, то WPF всегда возвращается в непротиворечивое состояние пос# ле возникновения исключения. Это означает, что разработчик может выполнять собственную логику восстановления, предполагая, что сама платформа не пострадала.

Не так уж трудно написать код обработки ошибок при вызове метода. Для это#

го применяется конструкция try/catch/finally. Проблема  в том, что делать, когда никто не обрабатывает  исключение, – либо потому что оно возникло асинхронно, либо потому что осталось не перехваченным.  В этом случае WPF по умолчанию аварийно  завершает приложение.

* Цитата из стихотворения Г. Лонгфелло «The rainy day» (перевод Ч. Самуил) (Прим. перев.)

7  В CLR есть механизм — область ограниченного выполнения (CER), который гарантирует возмож) ность выполнения даже при отсутствии памяти. Код, исполняемый внутри CER, не должен выделять память, упаковывать значения и вызывать методы, не являющиеся частью CER. Писать такой код очень трудно, поэтому он обычно встречается лишь на самых нижних уровнях платформы (менее сотни методов во всем каркасе .NET пользуются CER). Вместо этого WPF задействует механизм вентилей памяти (memory gate), который приводит к возбуждению восстановимого исключения еще до того, как ресурсы будут полностью исчерпаны. Это прогностический механизм (производит) ся оценка объема доступных ресурсов, и отказ случается еще до катастрофической нехватки памя) ти), поэтому гарантировать успех во всех случаях он не может. Задача вентилей памяти — умень) шить вероятность возникновения ситуации, когда памяти больше нет вообще. Хотя мы сделали все возможное, чтобы избежать невосстановимых исключений, особого интереса они не представляют. Коль скоро восстановление все равно невозможно, процесс просто завершается.

Объект Application  предоставляет событие DispatcherUnhandledException8, которое соответствует  именно таким обстоятельствам. Подписавшись на это со# бытие, приложение может реализовать любую политику  обработки исключений, достигших уровня самого приложения:

<!— MyApp.xaml —>

<Application … DispatcherUnhandledException=’Failure’ />

// MyApp.xaml.cs

void Failure(object sender, DispatcherUnhandledExceptionEventArgs e)

{

// прикладная логика

}

Диспетчер  – это часть системы, отвечающая  за отправку  событий  и сообще# ний различным компонентам.  Рассматриваемое событие посылается,  когда дис# петчер видит необработанное приложением исключение. В аргументе типа DispatcherUnhandledExceptionEventArgs передается  самое возникшее  исключе# ние, а также флаг Handled,  которому можно присвоить значение true и тем самым показать,  что исключение  следует игнорировать, а приложение должно  продол# жать работу:

public class DispatcherUnhandledExceptionEventArgs

: DispatcherEventArgs {

public Exception Exception { get; }

public bool Handled { get; set; }

}

Мы могли бы реализовать тактику, состоящую в том, что любая ошибка игно# рируется,  но протоколируется, а пользователю предлагается отправить  отчет об ошибке системному  администратору:

void Failure(object sender, DispatcherUnhandledExceptionEventArgs e)

{

using (StreamWriter errorLog =

new StreamWriter(«c:\\error.log», true))

{

errorLog.WriteLine(«Error @ « + DateTime.Now.ToString(«R»));

errorLog.WriteLine(e.Exception.ToString());

}

e.Handled = true;

MessageBox.Show(«Произолша ошибка, отправьте системному « +

«администратору содержимое файла ‘c:\\error.log’»);

«contents of the file ‘c:\\error.log’»);

}

8 Здесь слово dispatcher относится к высокоуровневому объекту обработки сообщений, который и делает всю работу внутри метода Application.Run.

К тому моменту, когда исключение преобразуется в событие Dispatcher UnhandledException, у автора  приложения остается  не так уж много вариантов действий; о контексте, в котором произошла ошибка, известно очень мало (только сам объект  исключения), и повторить  операцию  уже невозможно (если  только внутреннее состояние не говорит о том, что было предпринято ранее). По сути де# ла, приложение может как#то сообщить об исключении, проигнорировать его, ава# рийно завершиться или выполнить ту или иную комбинацию  вышеописанного.

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

Управление состоянием

Состояние приложения, как правило, сосредоточено  в компонентах  пользова# тельского интерфейса верхнего уровня. Например, бывает нужно поддерживать спи# сок открытых документов или следить за текущим состоянием  сетевого соединения. Хранить  состояние  удобно в объекте Application,  поскольку  в этом случае оно дос# тупно глобально (с помощью статического свойства Application.Current) на протяже# нии всего времени  работы  приложения. Простейший способ сохранить  состояние приложения – воспользоваться свойством Properties объекта Application.  Оно имеет тип System.Collections.IDictionary, следовательно, в нем можно хранить любой объ# ект, сопоставив ему в качестве ключа любой другой объект, обычно строку.

Продолжая приведенный выше пример  обработки  ошибок, мы можем кэши#

ровать последнюю возникшую  в приложении ошибку:

// myapp.xaml.cs

public partial class MyApp : Application {

..

void Failure(object sender, DispatcherUnhandledExceptionEventArgs e) {

using (StreamWriter errorLog =

new StreamWriter(«c:\\error.log», true)) { errorLog.WriteLine(«Error @ « + DateTime.Now.ToString(«R»)); errorLog.WriteLine(e.Exception.ToString());

}

e.Handled = true;

this.Properties[«LastError»] = e.Exception;

}

..

}

Поскольку Properties – просто словарь, в котором и ключ, и значение  имеют тип object, то приходится  постоянно  выполнять приведение  типов. Вместо этого можно определить  свойство или поле в самом объекте Application:

// myapp.xaml.cs

public partial class MyApp : Application {

private Exception _lastError;

public Exception LastError { get { return _lastError; } set { _lastError = value; }

}

void Failure(object sender, DispatcherUnhandledExceptionEventArgs e) {

using (StreamWriter errorLog =

new StreamWriter(«c:\\error.log», true)) {

errorLog.WriteLine(«Error         @          «         + DateTime.Now.ToString(«R»));

errorLog.WriteLine(e.Exception.ToString());

}

}

}

e.Handled = true;

this.LastError = e.Exception;

По мере расширения состояния приложения начинают возникать  общие воп# росы. Следует  ли сохранять  состояние  между запусками  приложения? Связано ли состояние с конкретным пользователем? Задается ли состояние только на эта# пе компиляции (например, изображение)? Свойства  объекта Application  не дают ответов на эти вопросы; для многих видов состояния приходится  обращаться  к ресурсам и службам конфигурирования.

Источник: К. Андерсон  Основы  Windows Presentation Foundation. Пер. с англ. А. Слинкина — М.: ДМК Пресс, 2008 — 432 с.: ил.

По теме:

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