Здравствуйте,
уважаемые читатели моего блога. В этой статье мы рассмотрим далее варианты
использования библиотеки Prism 5. В этот раз мы уделим внимание навигации,
которая базируется на состоянии (VisualState) и имеет название State-Based
Navigation. Термин "навигация" определён как процесс, в котором
приложение координирует изменения UI в ответ на жесты пользователя или
изменения внутреннего состояния приложения.
При навигации на
основе состояний, представление обновляется как при изменениях состояния модели
представлении, так и при взаимодействии пользователя с самим представлением.
При этом, вместо того, чтобы заменять представление другим представлением,
просто меняется его состояние. В зависимости от того, как меняется состояние
представления, это может восприниматься пользователем как навигация.
Этот стиль навигации хорошо подходит в следующих случаях:
- Представлению нужно отобразить те же самые данные или функционал, но в другом виде или формате.
- Представление должно изменить свою разметку, или стиль, в ответ на изменение состояния модели представления
- Представление должно произвести модальное, или немодальное взаимодействие с пользователем, без смены контекста представления.
Иногда
нужно показать одни и те же данные в разных стилях. Примером
использования такой анимации может служить тот, который идет в поставке с
библиотекой Prism
5 – State-Based Navigation QuickStart.
В
данном примере есть анимированный переход, в зависимости от выбранного типа
анимации, как показано на рисунке ниже.
Так
как в данном примере представление использует одну и туже модель для привязки,
но отображает эти данные по-разному, то модель представления, по сути, в данном
процессе не участвует как таковая. Она не выполняет каких-либо действий.
Вся работа ложится на xaml
и
на наши знания Expression
Blend.
В примере, который идет в поставке, используется
DataStateBehavior
для переключения между визуальными состояниями, определёнными
в менеджере визуальных состояний (visual state manager),
с помощью радиокнопок. Одна кнопка включает представление контактов в виде
списка, другая — в виде аватарок.
<ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
Value="True"
TrueState="ShowAsList"
FalseState="ShowAsIcons"/>
Когда пользователь нажимает радиокнопку Contacts или Avatar, визуальное состояние переключается между
Теперь второй нюанс, который я хотел бы донести
до читателей данной статьи. Поскольку я больше являюсь backend developer, для меня сложно рисовать красивую анимацию в Expression Blend, поэтому анимацию, использованную в статье, я позаимствовал с
примера State-Based Navigation QuickStart, который идет в поставке, и
адаптировал его под свои нужды. Если вы умеете хорошо использовать VisualStates, Transition и другие
возможности анимации на основании состояний, вы сможете более элегантно
сделать анимацию под ваши нужды. А пока, особо не заморачиваясь, создадим
по старинке новый WPF проект и назовем его StateBasedNavigationSample. ShowAsList
и ShowAsIcons
состоянием. Анимация флип-перехода также определена в
менеджере визуальных состояний. Для более детального ознакомления с анимацией
на основании состояний вы можете ознакомиться на habrahabr в статье "Руководство
разработчика Prism – часть 8, навигация", которая описывает возможности Prism 4.1. Но поскольку в данном аспекте ничего не изменилось, по
сравнению с Prism 5, для ознакомления этой информации будет достаточно. Либо можете
посмотреть в документации к Prism 5 ("8:
NavigationUsing the Prism Library 5.0 for WPF") на английском,
которая больше затрагивает изменения, которые коснулись второго типа анимации,
основанной на изменении модели и которая в Prism называется навигацией на основе представлений (View-Based Navigation). Этот тип навигации мы рассмотрим в следующей статье, поэтому для вас
будет достаточно той информации, которая приведена по ссылке с сайта habrahabr.
Я
для создания использовал .NET Framework 4.5.1, но вы можете выбрать минимальный .NET Framework 4.5. С меньшей версией фреймворка у вас
не станет Prism 5. Затем по аналогии, как описано в статье "Введение в Prism 5. Bootstrapper", реализуем
начальную структуру проекта. Ниже вы можете увидеть базовую структуру проекта,
которая получилась у меня.
Проект построен вокруг
классического паттерна MVVM,
и с Prism
5 взято самую крохотную часть.
- Assets – хранится информация о стилях;
- Models ‒ хранится информация о моделях проекта;
- ViewModels – здесь мы храним информацию о модели представления;
- Views – хранится информация о представлениях.
Поскольку я часть
логики взял с примеров, которые идут в Prism 5, то для того чтобы не создавать стиль
непосредственно в контролах, как это было сделано в примере State-Based Navigation с
поставки, я
вынес их отдельно в папку Assets.
Давайте добавим в папку Assets
новый
словарь ресурсов Resource
Dictionary
и
назовем его Style.xaml.
Затем в созданный файл
ресурсов добавим нужные стили для наших контролов.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="PrimaryColor">#FF63AADA</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}" />
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Style x:Key="ContactsList" TargetType="ListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Author}" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Title}" Margin="0,0,10,0" FontStyle="Italic"/>
<TextBlock Text="{Binding Price, StringFormat='{}{0:C}'}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle}"/>
</Style>
<Style x:Key="AvatarsList" TargetType="ListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="10">
<Image Margin="0,0,10,0" Width="75" Height="75" Source="{Binding AvatarUri}"/>
<TextBlock Text="{Binding Author}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
</Style>
</ResourceDictionary>
Затем сделаем этот
словарь ресурсов доступным всему приложению (поскольку у нас этот ресурс один, такой подход позволительный.) Для этого перейдем в файл App.xaml и
немного подправим реализацию.
<Application x:Class="StateBasedNavigationSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Assets/Style.xaml"
/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Поскольку я по старинке
создаю во всех проектах модель данных Book, в которой хранится информация о книгах, я добавил
сразу картинки тех книг, которые будут использованы в примере сразу в ресурсы.
Это картинки таких книг, как "C# in Depth", "CLR via C# (Developer Reference)" и "Refactoring:
Improving the Design of Existing Code". Все эти картинки взяты с Google и просто добавлены в проект. Вы
можете поступить так же или скачать проект, который будет доступен в конце
статьи, и посмотреть готовую реализацию.
Теперь пора вернуться к нашей модели Book, в которую добавилось новое поле AvatarUri для хранения пути к картинке (хотя более верным, на мой взгляд, было бы название Cover, только я решил оставить первоначальный вариант, который я "одолжил" у ребят с patterns & practices). Реализация данной модели приведена ниже.
Теперь пора вернуться к нашей модели Book, в которую добавилось новое поле AvatarUri для хранения пути к картинке (хотя более верным, на мой взгляд, было бы название Cover, только я решил оставить первоначальный вариант, который я "одолжил" у ребят с patterns & practices). Реализация данной модели приведена ниже.
public class Book : BindableBase
{
private long _id;
public long Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged(() => Id);
}
}
private string _author;
public string Author
{
get { return _author; }
set
{
_author = value;
OnPropertyChanged(() => Author);
}
}
private string _avatarsUri;
public string AvatarUri
{
get { return _avatarsUri; }
set
{
_avatarsUri = value;
OnPropertyChanged(() => AvatarUri);
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged(() => Title);
}
}
private DateTime _year;
public DateTime Year
{
get { return _year; }
set
{
_year = value;
OnPropertyChanged(() => Year);
}
}
private string _sn;
public string SN
{
get { return _sn; }
set
{
_sn = value;
OnPropertyChanged(() => SN);
}
}
private double _price;
public double Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged(() => Price);
}
}
}
Теперь
создадим в папке ViewModels модель представления ShellViewModel , которая, по сути, просто создаст коллекцию книг и заполнит ее какими-то данными.
Ниже представлена реализация этой модели представления.
public class ShellViewModel
{
public ShellViewModel()
{
Initialize();
}
private void Initialize()
{
Books = new ObservableCollection<Book>(GenerateBooks());
}
#region
Public Properties
public ObservableCollection<Book> Books { get; set; }
#endregion
#region
Static Methods
public static IEnumerable<Book> GenerateBooks()
{
yield return new Book { Id = 1,
Author = "Jon Skeet",
Title = "C# in Depth",
Price = 22.5,
SN = "ISBN: 9781617291340",
Year = new DateTime(2013, 9, 10),
AvatarUri = @"/StateBasedNavigationSample;component/Assets/csharp_in_deps.jpg"
};
yield return new Book { Id = 2,
Author = "Martin Fowler",
Title = "Refactoring: Improving the
Design of Existing Code",
Price = 41.52,
SN = "ISBN-10: 0201485672",
Year = new DateTime(1999, 7, 8),
AvatarUri = @"/StateBasedNavigationSample;component/Assets/refectoring.jpg"
};
yield return new Book { Id = 3,
Author = "Jeffrey Richter",
Title = "CLR via C# (Developer
Reference)",
Price = 35,
SN = "ISBN-10: 0735667454",
Year = new DateTime(2012, 12, 4),
AvatarUri = @"/StateBasedNavigationSample;component/Assets/clr_via_csharp.jpg"
};
}
#endregion
}
Так как мы
не обрабатываем хоть какие-то действия пользователя, то у нас модель
представления, кроме связывания, не выполняет больше никаких действий. После проделанной работы перейдем в нашу оболочку (класс Shell.xaml) и
добавим в нее использование DataStateBehavior. Всякая анимация,
переключения между состояниями с помощью VisualStateManager, а также использование Viewport3D для
моделирования красивого переключения взято с примера State-Based Navigation, так как я
не умею создавать такие сложные и красивые стили в Expression Blend. У меня UI проектирование идет со скрипом, вернее, не само проектирование и композиция, они проблем не составляют, а именно наведение марафета контролам.
Реализация оболочки Shell.xaml приведена в примере ниже.
<Window x:Class="StateBasedNavigationSample.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Title="MainWindow" Height="350" Width="525">
<Grid>
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
Value="True"
TrueState="ShowAsList" FalseState="ShowAsIcons"/>
</i:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualizationStates">
<VisualStateGroup.Transitions>
<VisualTransition From="ShowAsIcons" To="ShowAsList">
<Storyboard SpeedRatio="2">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Angle" Storyboard.TargetName="rotate">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="360"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="270"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" >
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
<DiscreteObjectKeyFrame KeyTime="0:0:1">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
<DiscreteObjectKeyFrame KeyTime="0:0:1">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="ShowAsList" To="ShowAsIcons">
<Storyboard SpeedRatio="2">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Angle" Storyboard.TargetName="rotate">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="360"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="270"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" >
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
<DiscreteObjectKeyFrame KeyTime="0:0:1">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
<DiscreteObjectKeyFrame KeyTime="0:0:1">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ShowAsList">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ShowAsIcons">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ContainerPane" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White"
BorderBrush="{StaticResource PrimaryBrush}"
BorderThickness="2" Margin="2,2,2,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Center" Orientation="Horizontal">
<RadioButton x:Name="ShowAsListButton" IsChecked="True"
Content="List"
Margin="5">
</RadioButton>
<RadioButton
x:Name="ShowAsIconsButton"
Content="Icons"
Margin="5"/>
</StackPanel>
<!--
Contacts view-->
<ListBox x:Name="Contacts"
ItemsSource="{Binding Books}"
Style="{Binding Source={StaticResource ContactsList}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Row="1" Visibility="Collapsed"/>
<!-- Avatars view-->
<ListBox x:Name="Avatars"
ItemsSource="{Binding Books}"
Style="{Binding Source={StaticResource AvatarsList}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1" Visibility="Collapsed"
/>
</Grid>
</Border>
<Rectangle x:Name="spinnerBackground" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,0" Fill="White" />
<Viewport3D x:Name="spinner" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,0">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="0,0,0.5" LookDirection="0,0,-1" FieldOfView="90" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<DirectionalLight Color="#444" Direction="0,0,-1" />
<AmbientLight Color="#BBB" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D TriangleIndices="0,1,2 2,3,0" TextureCoordinates="0,1 1,1 1,0 0,0" Positions="-0.5,-0.5,0
0.5,-0.5,0 0.5,0.5,0 -0.5,0.5,0" />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush Visual="{Binding ElementName=ContainerPane}" Stretch="Uniform" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="rotate" Axis="0,3,0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</Grid>
</Window>
У нас есть две
радиокнопки "List"
и "Icons", которые при нажатии на них через DataStateBehavior и разные тонкости дизайна просто меняют один контрол на второй,
используя для этого красивую анимацию. Поэтому в данном окне и вышло так много
кода, который, в основном, делает только анимацию.
Небольшая
поправка для разработчиков: для того чтобы у вас заработала строка
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
Value="True"
TrueState="ShowAsList" FalseState="ShowAsIcons"/>
</i:Interaction.Behaviors>
После проделанных
действий перейдем в наш загрузчик Bootstrapper и
немного изменим инициализацию нашей оболочки Shell, а
точнее, просто установим для нее свойство DataContext.
public class Bootstrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.DataContext =
Container.Resolve<ShellViewModel>();
App.Current.MainWindow.Show();
}
}
И последним этапом у
нас будет переопределения метода OnStartup
в
классе App.xaml.cs, задачей
которого будет просто создать наш загрузчик и запустить его.
protected override void OnStartup(StartupEventArgs e)
{
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
И при нажатии на кнопку
"Icons"
мы получим следующий результат:
При нажатии на кнопку
мы можем увидеть интересный эффект при переключении между контролами. Полный
исходный код приложения вы можете скачать по ссылке StateBasedNavigationSample.
Если у вас есть неплохой опыт в использовании Expression Blend, то
для вас навигация на основе состояний будет очень легкой. Если у вас после
прочтения статьи возникли какие-то нюансы и недопонимания в использовании State-Based Navigation, постараюсь
на них ответить.
No comments:
Post a Comment