Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 10:

Проектирование приложения с учетом использования единого опыта разработки для настольных и Web-проектов

Аннотация: В лекции рассматривается подходы к разработке Silverlight/WPF приложений с максимальным разделением общего кода и XAML разметки.

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

Подходы к решению задачи

Как и многие другие вопросы проблему разделяемого Silverlight/WPF кода можно решить несколькими способами:

  • Разрабатывать кроссплатформенный проект с самого начала;
  • Сначала разработать продукт для одной платформы, а затем адаптировать его для другой.

Разработка кроссплатформенного проекта с самого начала

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

Разработка для одной платформы с последующим портированием на другую

Исходя из сказанного выше, наиболее вероятно, что вам придется следовать этим путем. Здесь перед вами встает выбор: начать с WPF или Silverlight. Безусловно, могут быть определенные стратегические, религиозные или какие-либо другие причины начать с одного или другого, но, пожалуй, в первую очередь стоит обратить внимание на технические аспекты.

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

Если же начать с WPF, то наиболее вероятно, что при портировании на Silverlight придется поплатиться за все удобства WPF и полностью переработать части кода, использующие недостающий в Silverlight функционал.

Инструментарий

Существует несколько способов организовать синхронное использование кода между Silverlight и WPF версиями приложения так, чтобы при этом было возможно реализовать некоторые участки кода специфично для определенной платформы. Наиболее удобный из них – использование ссылок на файлы для общего функционала и локализация различий при помощи директив препроцессора (в случае, если отличается небольшая область кода) и разделяемых классов (для серьезных отличий).

Создание ссылок на файлы в Visual Studio

Чтобы добавить ссылку на файл в WPF или Silverlight проект в Visual Studio достаточно в меню "Project" вызвать пункт "Add Existing Item…", перейти в папку другого проекта (Silverlight или WPF), выбрать файл и нажать "Add As Link" в меню кнопки "Add".

Меню добавления нового элемента в проект

Рис. 15.1. Меню добавления нового элемента в проект
Выпадающая кнопка для добавления ссылки на файл

Рис. 15.2. Выпадающая кнопка для добавления ссылки на файл

Создание ссылок на XAML файлы

При использовании ссылок на C# или VB файлы компилятор обрабатывает их так же, как и при использовании обычных копий файлов. В случае с XAML возникают некоторые проблемы, которые возможно обойти, добавив немного лишнего кода.

Предположим, что существует WPF проект, в котором определен пользовательский элемент управления MyTemplatedControl. Он не содержит никакой логики, всего лишь устанавливает стиль по умолчанию при помощи свойства DefaultStyleKey:

   1: public class MyTemplatedControl : Control
   2: {
   3:     public MyTemplatedControl()
   4:     {
   5:         this.DefaultStyleKey = typeof(MyTemplatedControl);
   6:     }
   7: }

В файле Generic.xaml, находящемся в папке Themes данного проекта, в стиле по умолчанию объявлен шаблон для данного элемента управления:

Пользовательский элемент управления и файл с его стилем по умолчанию

Рис. 15.3. Пользовательский элемент управления и файл с его стилем по умолчанию

В соответствии с этим шаблонов элемент управления MyTemplatedControl должен отображать синий прямоугольник – так можно легко проверить, что шаблон успешно найден и применен:

   1: <ResourceDictionary
   2:xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:local="clr-namespace:TestControl"
   5:     >
   6:  
   7:     <Style TargetType="local:MyTemplatedControl">
   8:         <Setter Property="Template">
   9:             <Setter.Value>
  10:                 <ControlTemplate 
  11:				TargetType="local:MyTemplatedControl">
  12:                     <Canvas>
  13:                         <Rectangle Canvas.Left="20"
  14:				     Canvas.Top="20" Width="100"
  15:				     Height="100" Fill="Blue"
  16:				     Stroke="Black" StrokeThickness="3" />
  17:                     </Canvas>
  18:                 </ControlTemplate>
  19:             </Setter.Value>
  20:         </Setter>
  21:     </Style>
  22: </ResourceDictionary>

Этот элемент управления добавлен на тестовое окно. Если запустить приложение, то окно будет выглядеть следующим образом:

Тестовое окно

Рис. 15.4. Тестовое окно

Предположим, что теперь необходимо создать Silverlight версию данного элемента управления. Для этого создается проект Silverlight, при помощи пункта "Add Existing Item…" и кнопки "Add As Link" добавляются ссылки на файл MyTemplatedControl.cs в корне проекта и файл Generic.xaml в папке Themed. Такой проект удачно скомпилируется, однако если добавить этот элемент управления в Silverlight приложение, на его месте будет показана пустая белая область – очевидно, что шаблон по умолчанию не был применен.

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

Решение данной проблемы – использование объединения словарей при помощи MergedDictionaries: необходимо переместить содержимое файла Generic.xaml в отдельный XAML файл (лучше, если будет создано по отдельному файлу для каждого элемента управления), и включить их при помощи MergedDictionaries в файл Generic.xaml.

MyTemplatedControl.xaml:

   1: <ResourceDictionary
   2: xmlns=
   3:	"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     xmlns:local="clr-namespace:TestControl"
   6:     >
   7:     <Style TargetType="local:MyTemplatedControl">
   8:         <Setter Property="Template">
   9:             <Setter.Value>
  10:                 <ControlTemplate
  11:				TargetType="local:MyTemplatedControl">
  12:                     <Canvas>
  13:                         <Rectangle Canvas.Left="20"
  14:				Canvas.Top="20" Width="100" Height="100" 
  15:               Fill="Blue" Stroke="Black" StrokeThickness="3" />
  16:                     </Canvas>
  17:                 </ControlTemplate>
  18:             </Setter.Value>
  19:         </Setter>
  20:     </Style>
  21: </ResourceDictionary>

Generic.xaml:

   1: <ResourceDictionary
   2:     xmlns=
   3:     "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     >
   6:     <ResourceDictionary.MergedDictionaries>
   7:         <ResourceDictionary
   8: Source="/WpfControl;component/Themes/MyTemplatedControl.xaml" />
   9:     </ResourceDictionary.MergedDictionaries>
  10: </ResourceDictionary>

Затем необходимо добавить файл MyTemplatedControl.xaml в Silverlight проект в качестве ссылки, а файл Generic.xaml – в виде копии, в которую дополнительно вносятся изменения.

Generic.xaml (версия для Silverlight)

   1: <ResourceDictionary
   2:xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     >
   5:     <ResourceDictionary.MergedDictionaries>
   6:         <ResourceDictionary 
   7:		Source="/SlControl;component/MyTemplatedControl.xaml" />
   8:     </ResourceDictionary.MergedDictionaries>
   9: </ResourceDictionary>

Обратите внимание на отличия в файле Generic.xaml для Silverlight:

  • Он ссылается на файлы в Silverlight сборке (SlControl);
  • Он ссылается на добавленные по ссылке файлы по их "некорректному" ресурсному ключу (обратите внимание на отсутствие ‘Themes’ в пути).

Теперь Silverlight приложение работает правильно:

Корректно работающее тестовое Silverlight приложение

Рис. 15.5. Корректно работающее тестовое Silverlight приложение

Таким образом возможно использовать общую XAML разметку в Silverlight и WPF приложениях без необходимости поддерживать 2 различные версии файла. Единственное, что для этого необходимо сделать – создать и поддерживать различные Generic.xaml в каждом проекте.

Директивы препроцессора

Когда между Silverlight и WPF версиями кода достаточно мало отличий, можно воспользоваться директивами препроцессора, чтобы включить тот или иной блок кода в зависимости от того, в каком проекте компилируется данный файл. Для удобства шаблон проекта Silverlight определяет константу "SILVERLIGHT". Конечно, возможно определить самостоятельно что-то более короткое и менее сложное в написании, однако надежнее оставить всё как есть – неизвестно, когда и где придется повторно использовать этот код.

Выглядеть это будет следующим образом:

   1: private TextBox _dataTextBox;
   2: public override void OnApplyTemplate()
   3: {
   4: #if SILVERLIGHT
   5:     _dataTextBox = this.GetTemplateChild("PART_DataTextBox")
   6:                      	as TextBox;
   7: #else
   8:     _dataTextBox = this.Template.FindName("PART_DataTextBox",
   9:				this) as TextBox;
  10: #endif
  11: }

Разделяемые классы

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

Простейшее приложение, созданное в соответствии с данными принципами, выглядит следующим образом:

Простейшее WPF/Silverlight приложение с разделяемыми классами

Рис. 15.6. Простейшее WPF/Silverlight приложение с разделяемыми классами

MyControlWPF – это WPF проект. Он содержит файл MyControl.cs, в котором определен разделяемый класс MyControl. Этот файл состоит из кода, который идентичен и в WPF, и в Silverlight. Он добавлен в MyControlSL (Silverlight проект) в качестве ссылки. Таким образом, достаточно поддерживать одну копию файла, который используется в обоих проектах. MyControl.WPF.cs и MyControl.SL.cs в свою очередь содержат члены класса MyControl, реализация которых различна в WPF и Silverlight.

Синхронное использование XAML

К сожалению, в случае XAML не существует встроенной поддержки директив препроцессора или схожего приема разделения отличных блоков кода. Конечно, существуют некоторые сторонние решения, однако хоть это и выглядит прогрессивно с точки зрения энтузиаста, кроме случаев действительно больших XAML файлов, сложных в поддержке, обычно более разумно работать с 2 файлами, если различия в разметке не удается обойти. Таким образом, следует либо составлять такую XAML разметку, которая будет корректна как в Silverlight, так и в WPF, и создать ссылку на этот файл в одном из проектов, либо поддерживать 2 параллельные версии файла.