WPF — Floppy Pages

от автора

Реализация нового Frame в стиле IOS

Или проще говоря — Frame в стиле Modern UI.

Здравствуйте. Меня зовут Андрей и я очень устал пользоваться стандартным VK на Windows 10. Его горизонтальная навигация меня утомила и как то она не вписывается в общий дизайн. Ещё очень давно хотел реализовать такое дело, а именно: плавная навигация как на iPhone. Для чего? Для того, что я хочу сделать свой VK клиент на WPF. Для начала покажу общую картину:

image

Можно сделать вывод, что такой подход будет очень удобным. DataContext между страницами будет передаваться через конструктор, но дальше будет интереснее.

Начну с namespace UFC.UI. Так как на каждой странице может находиться несколько кнопок, то мне пришлось создать интерфейс:

public delegate void FloppyPageNavigateEventHandler(IFloppyPage page, FloppyPageEventArgs e); public delegate void FloppyPageGoBackEventHandler(FloppyPageEventArgs e); public class FloppyPageEventArgs : EventArgs {     public FloppyPageEventArgs() { } }  public interface IFloppyPage {     event FloppyPageNavigateEventHandler Navigate;     event FloppyPageGoBackEventHandler GoBack;     IFloppyPages IFloppyPages { get; set; }     string Title { get; set; } } 

image

Каждая страница наследует этот интерфейс и получает очень удобное дополнение.

    public partial class Page1 : Page, IFloppyPage     {         public event FloppyPageNavigateEventHandler Navigate;         public event FloppyPageGoBackEventHandler GoBack;         public IFloppyPages IFloppyPages { get; set; }         public Page1() : this(null) { }         public Page1(object dataContext)         {             InitializeComponent();             if (dataContext != null)                 this.DataContext = dataContext;             else                 this.DataContext = this;             Title = "Первая страница";         }          private void NavigateTo_MainPage(object sender, RoutedEventArgs e)         {             if (Navigate != null)                 Navigate(new MainPage(DataContext), new FloppyPageEventArgs());         }          private void NavigateTo_Page2(object sender, RoutedEventArgs e)         {             if (Navigate != null)                 Navigate(new Page2(DataContext), new FloppyPageEventArgs());         }          private void NavigateTo_Page3(object sender, RoutedEventArgs e)         {             if (Navigate != null)                 Navigate(new Page3(DataContext), new FloppyPageEventArgs());         }          private void Button_GoBack(object sender, RoutedEventArgs e)         {             if (GoBack != null)                 GoBack(new FloppyPageEventArgs());         }     } 

Теперь плавно можно подойти к интересному. Здесь затронут интерфейс IFloppyPages. Конечно его можно было бы по другому назвать, но я выбрал именно такое название. Его функция ни чем не отличается от DataContext. Такое решение сделано для того, что бы в будущем мы могли использовать DataContext в других целях (mvvm, binding, commands и т.д.)
Собственно, вот его реализация:

public interface IFloppyPages {     IFloppyPage FirstPage { get; set; }     IFloppyPage CurrentPage { get; set; }     int JournalCount { get; set; }     void Navigate(IFloppyPage page);     bool GoBack();     bool CanGoBack { get; set; } } 

Пожалуй теперь можно взглянуть на xaml разметку этого элемента управления:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"                     xmlns:local="clr-namespace:UFC.UI.Controls">   <Thickness x:Key="Dynamic.LongPage.MarginAnimation">10, 0, -10, 0</Thickness>   <Style TargetType="local:FloppyPages">      <Style.Setters>          <Setter Property="Template">              <Setter.Value>                  <ControlTemplate TargetType="local:FloppyPages">                      <Grid Name="mainGrid">                          <Grid Name="grid1">                              <Frame Name="frame1" NavigationUIVisibility="Hidden"/>                          </Grid>                          <Grid Name="grid2">                              <Frame Name="frame2" NavigationUIVisibility="Hidden"/>                          </Grid>                          <Grid.Resources>                              <BeginStoryboard x:Key="grid1Animation">                                  <Storyboard>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid1"                                         Storyboard.TargetProperty="Margin"                                         From="{DynamicResource Dynamic.LongPage.MarginAnimation}"                                         To="0">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid2"                                         Storyboard.TargetProperty="Margin"                                         From="0"                                         To="-100, 20, 100, 20">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                  </Storyboard>                              </BeginStoryboard>                              <BeginStoryboard x:Key="grid2Animation">                                  <Storyboard>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid2"                                         Storyboard.TargetProperty="Margin"                                         From="{DynamicResource Dynamic.LongPage.MarginAnimation}"                                         To="0">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid1"                                         Storyboard.TargetProperty="Margin"                                         From="0"                                         To="-100, 20, 100, 20">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                  </Storyboard>                              </BeginStoryboard>                              <BeginStoryboard x:Key="grid3Animation">                                  <Storyboard>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid1"                                         Storyboard.TargetProperty="Margin"                                         From="0"                                         To="{DynamicResource Dynamic.LongPage.MarginAnimation}">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid2"                                         Storyboard.TargetProperty="Margin"                                         From="-100, 20, 100, 20"                                         To="0">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                  </Storyboard>                              </BeginStoryboard>                              <BeginStoryboard x:Key="grid4Animation">                                  <Storyboard>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid2"                                         Storyboard.TargetProperty="Margin"                                         From="0"                                         To="{DynamicResource Dynamic.LongPage.MarginAnimation}">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                      </ThicknessAnimation>                                      <ThicknessAnimation                                         Duration="0:0:0.8"                                         Storyboard.TargetName="grid1"                                         Storyboard.TargetProperty="Margin"                                         From="-100, 20, 100, 20"                                         To="0">                                          <ThicknessAnimation.EasingFunction>                                              <ElasticEase EasingMode="EaseOut" Oscillations="1"/>                                          </ThicknessAnimation.EasingFunction>                                       </ThicknessAnimation>                                  </Storyboard>                              </BeginStoryboard>                          </Grid.Resources>                      </Grid>                  </ControlTemplate>              </Setter.Value>          </Setter>      </Style.Setters>  </Style>      </ResourceDictionary> 

Очень надеюсь, что вам удастся понять мой алгоритм. Всё самое непонятное постараюсь объяснить после кода внизу страницы.

Теперь приведу весь код этого элемента управления:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Animation;  namespace UFC.UI {     /// <summary>     /// Страница по умолчанию.     /// </summary>     internal class DefaultPage : IFloppyPage     {         public event FloppyPageNavigateEventHandler Navigate;         public event FloppyPageGoBackEventHandler GoBack;         public IFloppyPages IFloppyPages { get; set; }         public string Title { get; set; }         public DefaultPage()         {             Title = "Страница по умолчанию";         }     }      public delegate void FloppyPageNavigateEventHandler(IFloppyPage page, FloppyPageEventArgs e);     public delegate void FloppyPageGoBackEventHandler(FloppyPageEventArgs e);     public class FloppyPageEventArgs : EventArgs     {         public FloppyPageEventArgs() { }     }      public interface IFloppyPage     {         event FloppyPageNavigateEventHandler Navigate;         event FloppyPageGoBackEventHandler GoBack;         IFloppyPages IFloppyPages { get; set; }         string Title { get; set; }     }      public interface IFloppyPages     {         IFloppyPage FirstPage { get; set; }         IFloppyPage CurrentPage { get; set; }         int JournalCount { get; set; }         void Navigate(IFloppyPage page);         bool GoBack();         bool CanGoBack { get; set; }     } }  namespace UFC.UI.Controls {     public class FloppyPages : Control, IFloppyPages, INotifyPropertyChanged     {         #region Private Members          private bool GridNumber = false;         private bool IsDoneAnimation = true;         private List<IFloppyPage> journal = new List<IFloppyPage>();          private Frame frame1 = new Frame();         private Frame frame2 = new Frame();         private Grid mainGrid = new Grid();         private Grid grid1 = new Grid();         private Grid grid2 = new Grid();          private BeginStoryboard animation1 = new BeginStoryboard()         { Storyboard = new Storyboard() };         private BeginStoryboard animation2 = new BeginStoryboard()         { Storyboard = new Storyboard() };         private BeginStoryboard animation3 = new BeginStoryboard()         { Storyboard = new Storyboard() };         private BeginStoryboard animation4 = new BeginStoryboard()         { Storyboard = new Storyboard() };          #endregion          #region Constructors         static FloppyPages()         {             DefaultStyleKeyProperty.OverrideMetadata(typeof(FloppyPages),                 new FrameworkPropertyMetadata(typeof(FloppyPages)));              FloppyPages.NavigatedRoutedEvent =                 EventManager.RegisterRoutedEvent("Navigated", RoutingStrategy.Bubble,                 typeof(RoutedEventHandler), typeof(FloppyPages));             FloppyPages.WentBackRoutedEvent =                 EventManager.RegisterRoutedEvent("WentBack", RoutingStrategy.Bubble,                 typeof(RoutedEventHandler), typeof(FloppyPages));         }          public FloppyPages()         {             FirstPage = new DefaultPage();         }          #endregion          #region Public Dependency Properties                  public static readonly DependencyProperty FirstPageProperty =             DependencyProperty.RegisterAttached("FirstPage", typeof(IFloppyPage),                 typeof(FloppyPages));          #endregion          #region Public Properties         public IFloppyPage FirstPage         {             get { return (IFloppyPage)GetValue(FirstPageProperty); }             set             {                 SetValue(FirstPageProperty, value);                 OnFirstPage(FirstPage);                 OnPropertyChanged("FirstPage");             }         }          #endregion          #region Public RoutedEvents          public static readonly RoutedEvent NavigatedRoutedEvent;         public static readonly RoutedEvent WentBackRoutedEvent;          #endregion          #region Public Events          public event RoutedEventHandler Navigated         {             add { base.AddHandler(FloppyPages.NavigatedRoutedEvent, value); }             remove { base.RemoveHandler(FloppyPages.NavigatedRoutedEvent, value); }         }          public event RoutedEventHandler WentBack         {             add { base.AddHandler(FloppyPages.WentBackRoutedEvent, value); }             remove { base.RemoveHandler(FloppyPages.WentBackRoutedEvent, value); }         }          #endregion          #region Public Members         public IFloppyPage CurrentPage         {             get             {                 if (journal.Count > 0)                     return journal[journal.Count - 1];                 else                     return null;             }             set { }         }          public int JournalCount         {             get             {                 return journal.Count;             }             set { }         }          public void Navigate(IFloppyPage page)         {             Start_Navigate(page);         }          public bool GoBack()         {             return Start_GoBack();         }          public bool CanGoBack         {             get             {                 if (journal.Count > 1)                     return true;                 else                     return false;             }             set { }         }          #endregion          #region Private OnFirstPage         private void OnFirstPage(IFloppyPage page)         {             if (page != null && frame1 != null && frame2 != null)             {                 if (GridNumber)                     frame1.Navigate(page);                 else                     frame2.Navigate(page);                 page.Navigate += Page_Navigate;                 page.GoBack += Page_GoBack;                 journal.Clear();                 journal.Add(page);                                  OnPropertyChanged("JournalCount");                 OnPropertyChanged("CanGoBack");                 OnPropertyChanged("CurrentPage");             }         }          #endregion          #region Public OnApplyTemplate         public override void OnApplyTemplate()         {             base.OnApplyTemplate();              mainGrid = GetTemplateChild("mainGrid") as Grid;              grid1 = GetTemplateChild("grid1") as Grid;             if (grid1 != null)                 grid1.Margin = new Thickness(0);              grid2 = GetTemplateChild("grid2") as Grid;             if (grid2 != null)                 grid2.Margin = new Thickness(this.ActualWidth, 0, (-1 * this.ActualWidth), 0);              frame1 = GetTemplateChild("frame1") as Frame;             frame2 = GetTemplateChild("frame2") as Frame;              animation1 = mainGrid.Resources["grid1Animation"] as BeginStoryboard;             animation2 = mainGrid.Resources["grid2Animation"] as BeginStoryboard;             animation3 = mainGrid.Resources["grid3Animation"] as BeginStoryboard;             animation4 = mainGrid.Resources["grid4Animation"] as BeginStoryboard;              if (animation1 != null)                 if (animation1.Storyboard != null)                     animation1.Storyboard.Completed += NewGridMargin_Completed;             if (animation2 != null)                 if (animation2.Storyboard != null)                     animation2.Storyboard.Completed += NewGridMargin_Completed;             if (animation3 != null)                 if (animation3.Storyboard != null)                     animation3.Storyboard.Completed += OldGridMargin_Completed;             if (animation4 != null)                 if (animation4.Storyboard != null)                     animation4.Storyboard.Completed += OldGridMargin_Completed;              if (mainGrid != null)             {                 mainGrid.SizeChanged += (sender, e) =>                 {                     Application.Current.Resources["Dynamic.LongPage.MarginAnimation"] =                     new Thickness(this.ActualWidth, 0, -1 * this.ActualWidth, 0);                 };             }              OnFirstPage(FirstPage);         }          #endregion          #region Private Events         private void Page_Navigate(IFloppyPage page, FloppyPageEventArgs e)         {             Start_Navigate(page);         }          private void Page_GoBack(FloppyPageEventArgs e)         {             Start_GoBack();         }          private void NewGridMargin_Completed(object sender, EventArgs e)         {             Set_NewMargin();         }          private void OldGridMargin_Completed(object sender, EventArgs e)         {             Set_OldMargin();         }          #endregion          #region Private Navigate         private void Start_Navigate(IFloppyPage page)         {             if (page != null && IsDoneAnimation)             {                 IsDoneAnimation = false;                 GridNumber = !GridNumber;                 page.Navigate += Page_Navigate;                 page.GoBack += Page_GoBack;                  if (!GridNumber)                 {                     animation1.Storyboard.Stop();                     frame2.Navigate(page);                     Panel.SetZIndex(grid1, 0);                     Panel.SetZIndex(grid2, 1);                     grid2.Visibility = Visibility.Visible;                     animation2.Storyboard.Begin();                 }                 else                 {                     animation2.Storyboard.Stop();                     frame1.Navigate(page);                     Panel.SetZIndex(grid2, 0);                     Panel.SetZIndex(grid1, 1);                     grid1.Visibility = Visibility.Visible;                     animation1.Storyboard.Begin();                 }                 journal.Add(page);                  OnPropertyChanged("JournalCount");                 OnPropertyChanged("CurrentPage");                 OnPropertyChanged("CanGoBack");                  base.RaiseEvent(new RoutedEventArgs(FloppyPages.NavigatedRoutedEvent, this));             }         }         private void Set_NewMargin()         {             if (!GridNumber)             {                 grid2.Margin = new Thickness(0);                 grid1.Margin = new Thickness(this.ActualWidth, 0, (-1 * this.ActualWidth), 0);                 grid1.Visibility = Visibility.Hidden;             }             else             {                 grid1.Margin = new Thickness(0);                 grid2.Margin = new Thickness(this.ActualWidth, 0, (-1 * this.ActualWidth), 0);                 grid2.Visibility = Visibility.Hidden;             }             IsDoneAnimation = true;         }          #endregion          #region Private GoBack         private bool Start_GoBack()         {             if (journal.Count > 1 && IsDoneAnimation)             {                 IsDoneAnimation = false;                 GridNumber = !GridNumber;                 grid1.Visibility = Visibility.Visible;                 grid2.Visibility = Visibility.Visible;                  if (!GridNumber)                 {                     animation4.Storyboard.Stop();                     grid2.Margin = new Thickness(0);                     frame2.Navigate(journal[journal.Count - 2]);                     animation3.Storyboard.Begin();                 }                 else                 {                     animation3.Storyboard.Stop();                     grid1.Margin = new Thickness(0);                     frame1.Navigate(journal[journal.Count - 2]);                     animation4.Storyboard.Begin();                 }                 journal.Remove(journal[journal.Count - 1]);                  OnPropertyChanged("JournalCount");                 OnPropertyChanged("CurrentPage");                 OnPropertyChanged("CanGoBack");                  base.RaiseEvent(new RoutedEventArgs(FloppyPages.WentBackRoutedEvent, this));                  return true;             }             else                 return false;         }         private void Set_OldMargin()         {             if (!GridNumber)             {                 Panel.SetZIndex(grid1, 0);                 Panel.SetZIndex(grid2, 1);                 grid1.Margin = new Thickness(this.ActualWidth, 0, (-1 * this.ActualWidth), 0);                 grid1.Visibility = Visibility.Hidden;             }             else             {                 Panel.SetZIndex(grid1, 1);                 Panel.SetZIndex(grid2, 0);                 grid2.Margin = new Thickness(this.ActualWidth, 0, (-1 * this.ActualWidth), 0);                 grid2.Visibility = Visibility.Hidden;             }             IsDoneAnimation = true;         }          #endregion          #region INotifyPropertyChanged Members          public event PropertyChangedEventHandler PropertyChanged;          private void OnPropertyChanged(string propertyName)         {             PropertyChangedEventHandler handler = PropertyChanged;              if (handler != null)                 handler(this, new PropertyChangedEventArgs(propertyName));         }          #endregion     } }  

Начну с того, что ещё ранее вы могли заметить в xaml разметке этого элемента ресурс:

«Dynamic.LongPage.MarginAnimation».

И очень странно, почему там стояли такие размеры: 10,0,-10,0;

На самом деле это не так важно, потому что при сборке мы автоматически подписываемся на событие SizeChanged элемента mainGrid в методе OnApplyTemplate().

if (mainGrid != null) {     mainGrid.SizeChanged += (sender, e) =>     {         Application.Current.Resources["Dynamic.LongPage.MarginAnimation"] =             new Thickness(this.ActualWidth, 0, -1 * this.ActualWidth, 0);     }; } 

Благодаря такой реализации мы получаем элемент управления, где анимация двигается на то расстояние, на которое мы укажем, то есть просто изменив размер окна.

Напомню кстати говоря, что в методе OnApplyTemplate() мы получаем ссылки на все мелкие элементы из разметки методом GetTemplateChild(«mainGrid»);

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

Таким образом мы получаем две чередующие панели, на которых лежат frame1 и frame2. Благодаря переменной GridNumber мы проверяем, на какой grid мы попали и на каком frame поменять страницу.

Так же здесь реализован журнал, но в нём ничего интересного нет. Обычный список, который удаляет IFloppyPage только после перехода «Назад» (GoBack).

Да и ещё. Как только приложение начинает свою жизнь, ему присваивается первая страница, это может быть либо DefaultPage по умолчанию, либо та страница, которую укажете вы. Затем FloppyPages автоматически привяжет ваш IFloppyPage к событию Navigate и GoBack. Так он будет следить, когда на одной из ваших IFloppyPage вы решитесь перейти на другую страницу.

Теперь покажу окно, где и создаётся FloppyPages, и присваивается первая страница.

using System.Windows; using UFC.Pages; namespace UFC {     public partial class Browser : Window     {         public Browser()         {             InitializeComponent();             floppyPages.FirstPage = new MainPage()             {                 IFloppyPages = floppyPages             };         }          private void Button_GoBack(object sender, RoutedEventArgs e)         {             floppyPages.GoBack();         }     } } 

<Window x:Class="UFC.Browser"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         xmlns:ufc="clr-namespace:UFC.UI.Controls;assembly=UFC.UI"         Title="UFC" Height="640" Width="380" >     <Grid Background="LightGray">         <Grid.RowDefinitions>             <RowDefinition Height="40"/>             <RowDefinition/>         </Grid.RowDefinitions>                  <ufc:FloppyPages Grid.Row="1" Name="floppyPages" />          <Grid Grid.Row="0" Background="LightGray">             <Grid.ColumnDefinitions>                 <ColumnDefinition Width="40"/>                 <ColumnDefinition Width="1*"/>                 <ColumnDefinition Width="40"/>             </Grid.ColumnDefinitions>              <TextBox                 Grid.Column="1"                 VerticalContentAlignment="Center"                 HorizontalContentAlignment="Center"                 IsReadOnly="True"                 Background="Transparent"                 FontSize="20"                 Text="{Binding ElementName=floppyPages,                            Path=CurrentPage.Title,                            UpdateSourceTrigger=PropertyChanged}"/>             <Button                 Name="MenuButton"                 Grid.Column="0"                 Visibility="Visible">                 <Path                     Margin="5"                     Stretch="UniformToFill"                     Fill="Black"                     Data="F1 M 19,23L 27,23L 27,31L 19,31L 19,23 Z M 19,34L 27,34L 27,42L 19,42L 19,34 Z M 31,23L 57,23L 57,31L 31,31L 31,23 Z M 19,45L 27,45L 27,53L 19,53L 19,45 Z M 31,34L 57,34L 57,42L 31,42L 31,34 Z M 31,45L 57,45L 57,53L 31,53L 31,45 Z "/>             </Button>              <Button                 Name="BackButton"                 Grid.Column="0"                 Visibility="Hidden"                 Click="Button_GoBack">                 <Path                     Margin="5,9"                     Stretch="UniformToFill"                     Fill="Black"                     Data="F1 M 18.0147,41.5355C 16.0621,39.5829 16.0621,36.4171 18.0147,34.4645L 26.9646,25.5149C 28.0683,24.4113 29,24 31,24L 52,24C 54.7614,24 57,26.2386 57,29L 57,47C 57,49.7614 54.7614,52 52,52L 31,52C 29,52 28.0683,51.589 26.9646,50.4854L 18.0147,41.5355 Z M 47.5281,42.9497L 42.5784,37.9999L 47.5281,33.0502L 44.9497,30.4717L 40,35.4215L 35.0502,30.4717L 32.4718,33.0502L 37.4215,37.9999L 32.4718,42.9497L 35.0502,45.5281L 40,40.5783L 44.9497,45.5281L 47.5281,42.9497 Z "/>             </Button>                          <Button                 Grid.Column="2">                 <Path                     Margin="5,9"                     Stretch="UniformToFill"                     Fill="Black"                     Data="F1 M 57.9853,41.5355L 49.0354,50.4854C 47.9317,51.589 47,52 45,52L 24,52C 21.2386,52 19,49.7614 19,47L 19,29C 19,26.2386 21.2386,24 24,24L 45,24C 47,24 47.9317,24.4113 49.0354,25.5149L 57.9853,34.4645C 59.9379,36.4171 59.9379,39.5829 57.9853,41.5355 Z M 28.4719,42.9497L 31.0503,45.5281L 36,40.5784L 40.9498,45.5281L 43.5282,42.9497L 38.5785,37.9999L 43.5282,33.0502L 40.9498,30.4718L 36,35.4215L 31.0503,30.4718L 28.4719,33.0502L 33.4216,37.9999L 28.4719,42.9497 Z "/>             </Button>         </Grid>     </Grid> </Window> 

Такое проектное решение без всякого труда позволит вам добавить и ViewModel, и Model и возможность использовать один и тот же DataContext на разных страницах.

Спасибо за внимание.
ссылка на оригинал статьи https://habrahabr.ru/post/317896/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *