Monday, June 16, 2014

Введение в Prism 5. Навигация на основе состояний (State-Based Navigation)

Здравствуйте, уважаемые читатели моего блога. В этой статье мы рассмотрим далее варианты использования библиотеки 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, визуальное состояние переключается между 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.
Теперь второй нюанс, который я хотел бы донести до читателей данной статьи. Поскольку я больше являюсь backend developer, для меня сложно рисовать красивую анимацию в Expression Blend, поэтому анимацию, использованную в статье, я позаимствовал с примера State-Based Navigation QuickStart, который идет в поставке, и адаптировал его под свои нужды. Если вы умеете хорошо использовать VisualStates, Transition и другие возможности анимации на основании состояний, вы сможете более элегантно сделать анимацию под ваши нужды. А пока, особо не заморачиваясь, создадим по старинке новый WPF проект и назовем его StateBasedNavigationSample.
Я для создания использовал .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). Реализация данной модели приведена ниже.
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>
вам необходимо добавить ссылку на сборку System.Windows.Interactivity.
После проделанных действий перейдем в наш загрузчик 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