Freigeben über


Инфраструктура для разработки утилит

Опубликовано 14 апреля 2009 в 09:00:00 | Coding4Fun

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

Эйриен Калп (Arian Kulp), блог (EN).

Исходный текст: CodePlex.com

Сложность: средняя

Необходимое время: 4 часа

Цена: бесплатно

Необходимое ПО: Visual Basic или Visual C# Express Editions MEF

Введение

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

Для запуска приводимого кода необходимо иметь Visual Studio 2008 SP1 Express Edition или более продвинутый выпуск. Если у вас ее нет, загрузите отсюда: https://www.microsoft.com/express/ru. Для работы с основным проектом вы можете использовать Visual Basic или Visual C#, либо создать для данного проекта надстройки.

Что за инфраструктура?

Что же я на самом деле создаю? Все мы знаем, что базовый класс — отличное средство объединения тесно связанных объектов. Практический пример: базовый класс окна или элемента управления позволяет добавлять в свой собственный класс, создаваемый на его основе, уже существующие функции, не изобретая колесо заново. В Windows можно применять различные модели выполнения программы: службы, консольные приложения, гаджеты боковой панели, Windows-формы, WPF, COM и т. д. В каждой из них предоставляются некоторые готовые функции, такие как средства запуска и останова службы, цикл системных сообщений для Windows-форм и WPF.

Я написал немало небольших утилит за годы своей работы и понял, что копирую или заново пишу слишком много одного и того же кода. Лишняя работа! Поскольку я всегда создаю возникающий по таймеру уведомляющий значок, люблю убирать приложение в панель задач при минимизации окна и запоминаю его параметры, я реализую все эти вещи в каждом проекте. Может быть, мне следовало бы создать шаблон проекта в Visual Studio, но такой способ имеет свои недостатки. Если я добавляю к своей модели новую функцию, то в этом случае мне придется перекомпилировать и переделывать старые приложения, чтобы они воспользовались нововведением.

Есть способ получше — создать «базовое приложение». Это должно быть полноценное приложение со значком уведомления, меню и основным окном. При перемещении окна должны сохраняться его новые координаты. Затем отдельные утилиты могут подключаться к этому приложению в качестве подключаемых модулей (plugins). В начале я представлял себе модель, обеспечивающую подключение нескольких приложений, но при таком подходе возникают проблемы с приложениями, имеющими графический интерфейс. В конце концов, я остановился на многократно используемом приложении, к которому может подключаться единственная утилита.

Общий интерфейс пользователя

Я решил сделать это приложением WPF, поскольку в этом случае получал дополнительные возможности построения графического интерфейса и интерактивности. Я все еще пишу Winforms-программы, но предпочитаю WPF. Это отличный способ создавать свои приложения декларативным путем (при этом требуется XAML), а кроме того обеспечивается четкая структура программы. Если вы предпочитаете Windows Forms, вы можете применить компонент WindowsFormsHost для поддержки подключаемых модулей.

Базовое приложение не слишком сложное. Оно позволяет указать название приложения, которое будет появляться в меню панели задач и в качестве заголовка основного окна. Вы также можете указать, следует ли сворачивать приложение в значок панели задач. Если нет, то при нажатии кнопки, закрывающей приложение, оно действительно будет завершено. Минимизация всегда срабатывает стандартным способом.

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

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

clip_image002_2

Модульность

Как я уже говорил, одним из мотивов создания этого проекта было обеспечение обновления инфраструктуры без перекомпиляции самих утилит. Надо прояснить: это нельзя обеспечить в ста процентах случаев, но поскольку я не трогаю общий интерфейс, это должно работать нормально. А реализуется это с помощью Managed Extensibility Framework (MEF).

MEF позволяет без особых усилий обеспечить расширяемость приложений. Постройте свои интерфейсы или контракты, а использующие их подключаемые модули можно построить в виде отдельных сборок без статических связей. При запуске приложения вы указываете ему, где брать расширения, а оно само заботится об их загрузке. Контракт для подключаемых модулей (IWpfService) определяется следующим образом:

Visual C#

    1: public interface IWpfService
    2: {
    3:     // Получить пользовательский компонент для отображения
    4:     // в окне параметров управляющего приложения.
    5:     UserControl OptionsUserControl { get; }
    6:  
    7:     string Name { get; }
    8:     System.Drawing.Icon TrayIcon { get; }
    9:     string Description { get; }
   10:     string Author { get; }
   11:     Version Version { get; }
   12:     Uri AuthorUri { get; }
   13:     bool HideOnClose { get; }
   14:     string Status { get; set; }
   15:  
   16:     void Initialize();
   17:     void Start();
   18:     void Stop();
   19:  
   20:     event EventHandler StatusUpdated;
   21: }

VB

    1: Public Interface IWpfService
    2:     ' Получить пользовательский компонент для отображения
    3:     ' в окне параметров управляющего приложения.
    4:     ReadOnly Property OptionsUserControl() As UserControl
    5:  
    6:     ReadOnly Property Name() As String
    7:     ReadOnly Property TrayIcon() As System.Drawing.Icon
    8:     ReadOnly Property Description() As String
    9:     ReadOnly Property Author() As String
   10:     ReadOnly Property Version() As Version
   11:     ReadOnly Property AuthorUri() As Uri
   12:     ReadOnly Property HideOnClose() As Boolean
   13:     Property Status() As String
   14:  
   15:     Sub Initialize()
   16:     Sub Start()
   17:     Sub [Stop]()
   18:  
   19:     Event StatusUpdated As EventHandler
   20:  
   21: End Interface

UserControl — это составной элемент управления, содержащий все компоненты интерфейса пользователя, необходимые для настройки приложения. Это могут быть поля выбора файлов, флажки выбора параметров, кнопки запуска и останова потоков выполнения и другие необходимые элементы. Базовое приложение (хост) обеспечивает доступ к ним по запросу пользователя. Name, TrayIcon, Description и другие подобные свойства используются для предоставления информации, подобной той, что отображается в окне «О программе». Методы используются хостом для управления жизненным циклом программы, аналогично службам Windows. Метод Initialize() вызывается при запуске, а за ним следует вызов Start() . Stop() вызывается при отключении/закрытии. Управляемые хостом утилиты не должны заботиться об общей логике выполнения приложения, а лишь реализовывать собственные конкретные функции.

clip_image004_2

Проект ExtensibleWpfApp содержит базовый класс ApplicationWindow, производный от Window. Он может использоваться любым приложением, в котором нужно простое меню в панели задач, сохранение размеров и координат окна, а также возможность свертывания приложения в значок панели задач. В основе лежит класс MainAppWindow. Он наследуется от ApplicationWindow и также реализует IWpfHost. Этот класс обеспечивает создание основного окна, в котором отображаются метаданные и параметры приложения. Вы можете использовать любой из классов в качестве базового, но в последнем случае вы будете лишены возможностей расширяемости MEF.

Во время установки класс MainAppWindow также инициализирует себя и свой базовый класс с помощью свойств из подключаемого модуля:

Visual C#

    1: public MainAppWindow()
    2: {
    3:     InitializeComponent();
    4:  
    5:     Compose();
    6:  
    7:     // Установка параметров подключаемого модуля.
    8:     CurrentAddin.Start();
    9:  
   10:     this.ApplicationName = CurrentAddin.Name;
   11:     this.HideOnClose = CurrentAddin.HideOnClose;
   12:  
   13:     // Устанавливает пользовательский значок или оставляет стандартный.
   14:     if( CurrentAddin.TrayIcon != null )
   15:     {
   16:         this.TrayIcon = CurrentAddin.TrayIcon;
   17:     }
   18:  
   19:     dockPanelAddinInfo.DataContext = CurrentAddin;
   20:  
   21:     Application.Current.Exit += new ExitEventHandler(CurrentApp_Exit);
   22:     CurrentAddin.StatusUpdated += new EventHandler(CurrentAddin_StatusUpdated);
   23: }

VB

    1: Public Sub New()
    2:     InitializeComponent()
    3:  
    4:     Compose()
    5:  
    6:     ' Установка параметров подключаемого модуля.
    7:     CurrentAddin.Start()
    8:  
    9:     Me.ApplicationName = CurrentAddin.Name
   10:     Me.HideOnClose = CurrentAddin.HideOnClose
   11:  
   12:     ' Устанавливает пользовательский значок или оставляет стандартный.
   13:     If CurrentAddin.TrayIcon IsNot Nothing Then
   14:         Me.TrayIcon = CurrentAddin.TrayIcon
   15:     End If
   16:  
   17:     dockPanelAddinInfo.DataContext = CurrentAddin
   18:  
   19:     AddHandler Application.Current.Exit, AddressOf CurrentApp_Exit
   20:     AddHandler CurrentAddin.StatusUpdated, AddressOf CurrentAddin_StatusUpdated
   21: End Sub

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

Последующие шаги

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

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

Завершение

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

Повторное использование кода может быть различным, но в любом случае в начале надо потратить много времени. Любая хорошая программа рано или поздно пригодится еще, но метод копирования и вставки хуже повторного использования двоичных модулей (обращение к сборкам/DLL), а тот, в свою очередь, уступает привязке модулей в период выполнения. Такие инфраструктуры как MEF, System.Addin, PRISM и другие в значительной степени упрощают создание расширяемых приложений. Если подходить к делу с умом, можно наращивать функциональность своих программ посредством модулей других разработчиков. Вы можете сосредоточиться на реализации надежной основы, а другие предоставят вам популярные функции. И вам хорошо, и вашим пользователям!

Приступайте к работе прямо сейчас, предварительно скачав Visual Studio Express Edition, если ее у вас еще нет.

Об авторе
Avatar80_3

Эйриен Калп — разработчик ПО, проживающий на Среднем Западе США. Он начал писать программы в пятом классе (поначалу они были не очень профессиональные). Эйриен — энтузиаст своего дела, он принимает участие в работе INETA и Code Camps и следит за технологическими новинками. Он любит природу и в свободное время занимается фотографией и своей семьей.

Comments