Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
course_(Windows&Web).docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
4.68 Mб
Скачать

Краткие итоги

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

Литература Лабораторная работа 6. Построение кроссплатформенного Silverlight/wpf приложения.

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

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

Для более детального представления о можно скачать исходный код итогового проекта

Создание проектов

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

Окно создание проекта вызывается нажатием пункта меню File – New – Project:

Рисунок л.р. 6.1. Вызов окна создания проекта.

В появившемся окне необходимо перейти в раздел Other Project Types – Visual Studio Solutions и выбрать Blank Solution:

Рисунок л.р. 6.2. Создание пустого решения.

В созданное решение следует добавить Silverlight и WPF проекты, используя контекстное меню обозревателя решений:

Рисунок л.р. 6.3. Добавление проекта в решение.

Для добавления Silverlight приложения необходимо выбрать пункт Silverlight Application в разделе Visual C# – Silverlight:

Рисунок л.р. 6.4. Добавление Silverlight приложения.

В появившемся диалоговом окне следует подтвердить создание Web Application для размещения Silverlight приложения и выбрать версию Silverlight:

Рисунок л.р. 6.5. Задание параметров Silverlight приложения.

Аналогично необходимо добавить WPF приложение:

Рисунок л.р. 6.6. Добавление WPF приложения.

Вместе с проектами были автоматически созданы главные окна, MainWindow.xaml и MainPage.xml, для WPF и Silverlight соответственно. Так как эти сущности различны в двух версиях (в случае WPF это окно, в Silverlight – UserControl), их невозможно повторно использовать. Однако в них можно поместить приведенный в лекции #11 элемент управления ViewModelPresenter, который будет делегировать создание внутренней разметки представлению, соответствующему находящейся в DataContext модели представления.

MainWindow.xaml:

1: <Window x:Class="CrossPlatformApplication.MainWindow"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

4: xmlns:view="clr-namespace:CrossPlatformApplication"

5: Title="{Binding Title}" SizeToContent="WidthAndHeight">

6:

7: <view:ViewModelPresenter ViewModel="{Binding}" />

8: </Window>

MainPage.xaml:

1: <UserControl x:Class="SilverlightApplication.MainPage"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

4: xmlns:view="clr-namespace:CrossPlatformApplication">

5:

6: <view:ViewModelPresenter ViewModel="{Binding}" />

7: </UserControl>

Согласно принципам MVVM главному представлению приложения должна соответствовать модель представления. Для начала достаточно пустого класса, общего для WPF и Silverlight:

1: [Export]

2: [PartCreationPolicy(CreationPolicy.NonShared)]

3: [ExportMetadata(AopExtensions.AspectMetadata,

4: Aspects.NotifyPropertyChanged)]

5: public class MainViewModel : IEntitledViewModel

6: {

7: public string Title

8: {

9: get { return "Test Application"; }

10: }

11: }

Затем для корректного запуска приложения необходимо добавить логику инициализации IoC контейнера MEF и MVVM окружения, а также модифицировать логику отображения главных окон. Как в WPF, так и в Silverlight приложении модифицируется код App.xaml и App.xaml.cs.

App.xaml в WPF:

1: <Application x:Class="WpfApplication.App"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

4: <Application.Resources>

5:

6: </Application.Resources>

7: </Application>

App.xaml.cs в WPF:

1: public partial class App

2: {

3: #region Injected properties

4:

5: [Import]

6: public ChildViewManager ChildViewManager

7: { private get; set; }

8:

9: #endregion

10:

11: protected override void OnStartup(StartupEventArgs e)

12: {

13: base.OnStartup(e);

14:

15: // Create interception configuration

16: var cfg = new InterceptionConfiguration()

17: .AddAopInterception();

18:

19: var container = new CompositionContainer(

20: new InterceptingCatalog(new AggregateCatalog(

21: new DirectoryCatalog(".", "*.exe"),

22: new DirectoryCatalog(".", "*.dll")), cfg));

23:

24: var locator = new MefServiceLocator(container);

25: ServiceLocator.SetLocatorProvider(() => locator);

26: container.ComposeExportedValue<IServiceLocator>(locator);

27: container.ComposeParts(this);

28:

29: new MainWindow { DataContext =

30: locator.GetInstance<MainViewModel>() }.Show();

31: }

32: }

App.xaml в Silverlight:

1: <Application x:Class="SilverlightApplication.App"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

4: <Application.Resources>

5:

6: </Application.Resources>

7: </Application>

App.xaml.cs в Silverlight:

1: public partial class App

2: {

3: public App()

4: {

5: Startup += Application_Startup;

6: Exit += Application_Exit;

7: UnhandledException += Application_UnhandledException;

8:

9: InitializeComponent();

10: }

11:

12: #region Injected properties

13:

14: [Import]

15: public ChildViewManager ChildViewManager

16: { private get; set; }

17:

18: #endregion

19:

20: private void Application_Startup

21: (object sender, StartupEventArgs e)

22: {

23: // Create interception configuration

24: var cfg = new InterceptionConfiguration()

25: .AddAopInterception();

26:

27: var container = new CompositionContainer(

28: new InterceptingCatalog(

29: new DeploymentCatalog(), cfg));

30: var locator = new MefServiceLocator(container);

31: ServiceLocator.SetLocatorProvider(() => locator);

32: container.ComposeExportedValue<IServiceLocator>(locator);

33: container.ComposeParts(this);

34:

35: RootVisual = new MainPage

36: { DataContext = locator.GetInstance<MainViewModel>() };

37: }

38:

39: private void Application_Exit(object sender, EventArgs e)

40: {

41:

42: }

43:

44: private void Application_UnhandledException

46: (object sender, ApplicationUnhandledExceptionEventArgs e)

47: {

48: if (!System.Diagnostics.Debugger.IsAttached)

49: {

50: e.Handled = true;

51: Deployment.Current.Dispatcher.BeginInvoke(

52: delegate { ReportErrorToDOM(e); });

53: }

54: }

55:

56: private void ReportErrorToDOM

57: (ApplicationUnhandledExceptionEventArgs e)

44: {

45: try

46: {

47: string errorMsg = e.ExceptionObject.Message

48: + e.ExceptionObject.StackTrace;

49: errorMsg = errorMsg.Replace('"',

50: '\'').Replace("\r\n", @"\n");

51:

52: System.Windows.Browser.HtmlPage.Window.Eval(

53: "throw new Error(\"Unhandled Error in Silverlight Application "

54: + errorMsg + "\");");

55: }

56: catch (Exception)

57: {

58: }

59: }

60: }

Как видно из кода, главные окна приложения будут отображать представление MainView, соответствующее модели представления MainViewModel. Пусть это представление будет содержать простой текст «Hello world»:

1: <UserControl x:Class="CrossPlatformApplication.MainView"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

5: xmlns:mc

6: ="http://schemas.openxmlformats.org/markup-compatibility/2006"

7: mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">

8:

9: <Grid x:Name="LayoutRoot" Background="White" MinHeight="150"

10: MinWidth="200">

11: <TextBlock Text="Hello world"

12: HorizontalAlignment="Center"

13: VerticalAlignment="Center" />

14: </Grid>

15: </UserControl>

Естественно, как говорилось в лекции #12, необходимо создать представление в Silverlight проекте и добавить ссылку на него в WPF проект. Теперь оба приложения готовы к запуску и отображают на своем главном окне сообщение «Hello world».

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

В первую очередь необходимо создать модель представления диалога, которая наследуется от введенного в лекции #14 класса ModalViewModelBase:

1: [Export]

2: [PartCreationPolicy(CreationPolicy.NonShared)]

3: [ExportMetadata(AopExtensions.AspectMetadata,

4: Aspects.NotifyPropertyChanged)]

5: public class TextInputModalChildViewModel :

6: ModalChildViewModelBase

7: {

8: public virtual string Text { get; set; }

9: }

Далее создается представление, содержащее поле ввода, привязанное к свойству Text:

1: <UserControl

2: x:Class="CrossPlatformApplication.TextInputModalChildView"

3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

5: xmlns:i=

6: "http://schemas.microsoft.com/expression/2010/interactivity"

7: xmlns:ic=

8: "http://schemas.microsoft.com/expression/2010/interactions"

9: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

10: xmlns:mc=

11: "http://schemas.openxmlformats.org/markup-compatibility/2006"

12: mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">

13:

14: <StackPanel x:Name="LayoutRoot" Background="White">

15: <TextBlock Text="Введите новый текст" />

16: <TextBox Text="{Binding Text, Mode=TwoWay}" />

17: <StackPanel Orientation="Horizontal"

18: HorizontalAlignment="Right">

19: <Button Content="OK" Command="{Binding CloseCommand}"

20: Width="80" Height="30" Margin="6">

21: <i:Interaction.Triggers>

22: <i:EventTrigger EventName="Click">

23: <ic:ChangePropertyAction

24: PropertyName="ModalResult" Value="True"

25: TargetObject="{Binding}" />

26: </i:EventTrigger>

27: </i:Interaction.Triggers>

28: </Button>

29: <Button Content="Cancel"

30: Command="{Binding CloseCommand}" Width="80"

31: Height="30" Margin="6" />

32: </StackPanel>

33: </StackPanel>

34: </UserControl>

Здесь используется библиотека Expression Interactions, входящая в состав Microsoft Expression Blend 4, которая позволяет использовать кроссплатформенные Silverlight/WPF триггеры, схожие по функциональности с WPF триггерами, а также добавляют понятие поведения элемента управления, аналога которому в стандартной поставке WPF нет.

Далее, для работы с асинхронными моделями представления модальных дочерних окон удобно использовать библиотеку Reactive Extensions (Rx Framework):

1: public static class ObservableHelper

2: {

3: public static IObservable<EventPattern<EventArgs>>

4: ObserveClosed(this ICloseableViewModel childViewModel)

5: {

6: return Observable.FromEventPattern(

7: handler => childViewModel.Closed += handler,

8: handler => childViewModel.Closed -= handler);

9: }

10:

11: public static IObservable<T> SelectSender<T>

12: (this IObservable<EventPattern<EventArgs>> observable)

13: {

14: return observable.Select(ev => (T)ev.Sender);

15: }

16:

17: public static IObservable<T> WhereSucceeded<T>

18: (this IObservable<T> observable)

19: where T : IModalChildViewModel

20: {

21: return observable.Where(vm => vm.ModalResult.HasValue

22: && vm.ModalResult.Value);

23: }

24: }

1: public static class ViewModelExtension

2: {

3: /// <summary>

4: /// Resolves and shows Closeable View Model of

5: /// type <typeparamref name="T" />

6: /// </summary>

7: public static IObservable<T> ResolveAndShow<T>

8: (this IServiceLocator serviceLocator,

9: Action<T> prepareAction = null)

10: where T : ICloseableViewModel

11: {

12: var viewModel = serviceLocator.GetInstance<T>();

13:

14: if (prepareAction != null)

15: {

16: prepareAction(viewModel);

17: }

18:

19: IObservable<T> result = Observable.FromEventPattern

20: (handler => viewModel.Closed += handler,

21: handler => viewModel.Closed -= handler)

22: .SelectSender<T>();

23:

24: viewModel.Show();

25:

26: return result;

27: }

28: }

Класс ObservableHelper позволяет, с одной стороны конструировать IObservable последовательность из события закрытия окна, и с другой стороны предоставляет удобные LINQ-подобные методы для взаимодействия с данной последовательностью. Так, метод WhereSucceeded налагает на последовательность ограничение, которое позволяет ей выполниться лишь в случае завершения диалога с положительным ModalResult (что, как правило, происходит при подтверждении какой-либо операции).

Класс ViewModelExtension представляет метод, который одним вызовом разрешает закрываемую модель представления из IoC контейнера, показывает её и возвращает IObservable последовательность её закрытия.

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

1: [Export]

2: [PartCreationPolicy(CreationPolicy.NonShared)]

3: [ExportMetadata(AopExtensions.AspectMetadata,

4: Aspects.NotifyPropertyChanged)]

5: public class MainViewModel : EntitledViewModelBase

6: {

7: // Private fields

8: private string _text = "Hello world";

9:

10: public virtual string Text

11: {

12: get { return _text; }

13: protected set { _text = value; }

14: }

15:

16: #region Commands

17:

18: private ICommand _showTextInputCommand;

19:

20: public ICommand ShowTextInputCommand

21: {

22: get

23: {

24: return _showTextInputCommand ??

25: (_showTextInputCommand =

26: new ActionCommand(ShowTextInput));

27: }

28: }

29:

30: #endregion

31:

32: #region Injected properties

33:

34: [Import]

35: public IServiceLocator ServiceLocator { private get; set; }

36:

37: #endregion

38:

39: public override string Title

40: {

41: get { return "Test Application"; }

42: }

43:

44: private void ShowTextInput()

45: {

46: ServiceLocator

47: .ResolveAndShow<TextInputModalChildViewModel>()

48: .WhereSucceeded()

49: .Subscribe(vm => Text = vm.Text);

50: }

51: }

Соответственно, также изменяется разметка представления главного окна:

1: <UserControl x:Class="CrossPlatformApplication.MainView"

2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

5: xmlns:mc=

6: "http://schemas.openxmlformats.org/markup-compatibility/2006"

7: mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">

8:

9: <Grid x:Name="LayoutRoot" Background="White"

10: MinHeight="150" MinWidth="200">

11: <TextBlock Text="{Binding Text}"

12: HorizontalAlignment="Center"

13: VerticalAlignment="Center" />

14: <Button Content="Изменить"

15: Command="{Binding ShowTextInputCommand}"

16: Width="80" Height="30"

17: HorizontalAlignment="Right"

18: VerticalAlignment="Bottom" Margin="6" />

19: </Grid>

20: </UserControl>

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]