Читайте также:
|
|
В первую очередь необходимо создать проекты будущего приложения. В простейшем случае это будут 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>
Дата добавления: 2015-08-13; просмотров: 60 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Реализация MessageViewModel | | | ОТ АВТОРА |