WPF — Floppy Pages

Реалізація нового Frame в стилі IOS
Або простіше кажучи — Frame в стилі Modern UI.

Доброго дня. Мене звати Андрій і я дуже втомився користуватися стандартним VK на Windows 10. Його горизонтальна навігація мене втомила і як то вона не вписується в загальний дизайн. Ще дуже давно хотів реалізувати таку справу, а саме: плавна навігація як на iPhone. Для чого? Для того, що я хочу зробити свій VK клієнт на WPF. Для початку покажу загальну картину:
image
Можна зробити висновок, що такий підхід буде дуже зручним. DataContext між сторінками буде передаватися через конструктор, але далі буде цікавіше.

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


interface IFloppyPage
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
Кожна сторінка успадковує цей інтерфейс і отримує дуже зручне доповнення.

partial class Page1: Page, IFloppyPage
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 і т. д.)
Власне, ось його реалізація:

interface IFloppyPages
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 розмітку цього елемента керування:

local:FloppyPages
<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.ThicknessAnimation.Margin">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.ThicknessAnimation.Margin}"
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.ThicknessAnimation.Margin}"
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.ThicknessAnimation.Margin}">
<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.ThicknessAnimation.Margin}">
<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>



Дуже сподіваюся, що вам вдасться зрозуміти мій алгоритм. Все незрозуміле постараюся пояснити після коду внизу сторінки.

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

UFC.UI & UFC.UI.Controls
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace UFC.UI
{
#region DefaultPage

/// <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 = "Сторінка";
}
}
#endregion

#region IFloppyPage

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; }
}
#endregion

#region IFloppyPages
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; }
}
#endregion
}

namespace UFC.UI.Controls
{
#region FloppyPages
public class FloppyPages : Control, IFloppyPages, INotifyPropertyChanged
{
#region Private Members

private bool GridNumber = false;
private bool IsDoneAnimation = true;
private bool IsDoneInitialization = false;
private List<IFloppyPage> journal = new List<IFloppyPage>();

private Frame frame1 = null;
private Frame frame2 = null;
private Grid mainGrid = null;
private Grid grid1 = null;
private Grid grid2 = null;

private BeginStoryboard animation1 = null;
private BeginStoryboard animation2 = null;
private BeginStoryboard animation3 = null;
private BeginStoryboard animation4 = null;

#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() { }

#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 { /*Використовую Binding*/ }
}

public int JournalCount
{
get
{
return journal.Count;
}
set { /*Використовую Binding*/ }
}

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 { /*Використовую Binding*/ }
}

#endregion

#region Private OnFirstPage
private void OnFirstPage(IFloppyPage page)
{
if (page != null && IsDoneInitialization)
{
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.ThicknessAnimation.Margin"] =
new Thickness(this.ActualWidth, 0, -1 * this.ActualWidth, 0);
};
}
IsDoneInitialization = true;
FirstPage = new DefaultPage();
}

#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;
journal[journal.Count - 1].Navigate -= Page_Navigate;
journal[journal.Count - 1].GoBack -= Page_GoBack;

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
}
#endregion
}



Почну з того, що ще раніше ви могли помітити в xaml розмітці цього елемента ресурс:

«Dynamic.ThicknessAnimation.Margin».

І дуже дивно, чому там стояли такі розміри: 10,0,-10,0;

Насправді це не так важливо, тому що при складанні ми автоматично погоджуємося на подію SizeChanged елемента mainGrid в методі OnApplyTemplate().

if (mainGrid != null)
{
mainGrid.SizeChanged += (sender, e) =>
{
Application.Current.Resources["Dynamic.ThicknessAnimation.Margin"] =
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, і присвоюється перша сторінка. Хочу відразу попередити, що я робив акцент не на зовнішній дизайн і округлені кнопочки, а на структуру та її подальше використання.

partial class Browser: Window
using System.Windows;
using UFC.Pages;
namespace UFC
{
public partial class Browser : Window
{
public Browser()
{
InitializeComponent();
}

private void floppyPages_Navigated(object sender, RoutedEventArgs e)
{
if (floppyPages.CanGoBack)
BackButton.Visibility = Visibility.Visible;
}

private void floppyPages_WentBack(object sender, RoutedEventArgs e)
{
if (!floppyPages.CanGoBack)
BackButton.Visibility = Visibility.Hidden;
}

private void Button_GoBack(object sender, RoutedEventArgs e)
{
floppyPages.GoBack();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
floppyPages.FirstPage = new MainPage();
}
}
}



Window xClass UFC Browser
<x Window: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" Loaded="Window_Loaded">
<Grid Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>

<ufc:FloppyPages Grid.Row="1" Name="floppyPages" Navigated="floppyPages_Navigated" WentBack="floppyPages_WentBack"/>

<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,23 L 27,23 L 27,31 L 19,31 L 19,23 Z M 19,34 L 27,34 L 27,42 L 19,42 L 19,34 Z M 31,23 L 57,23 L 57,31 L 31,31 L 31,23 Z M 19,45 L 27,45 L 27,53 L 19,53 L 19,45 Z M 31,34 L 57,34 L 57,42 L 31,42 L 31,34 Z M 31,45 L 57,45 L 57,53 L 31,53 L 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.5355 C 16.0621,39.5829 16.0621,36.4171 18.0147,34.4645 L 26.9646,25.5149 C 28.0683,24.4113 29,24 31,24 L 52,24 C 54.7614,24 57,26.2386 57,29 L 57,47 C 57,49.7614 54.7614,52 52,52 L 31,52 C 29,52 28.0683,51.589 26.9646,50.4854 L 18.0147,41.5355 Z M 47.5281,42.9497 L 42.5784,37.9999 L 47.5281,33.0502 L 44.9497,30.4717 L 40,35.4215 L 35.0502,30.4717 L 32.4718,33.0502 L 37.4215,37.9999 L 32.4718,42.9497 L 35.0502,45.5281 L 40,40.5783 L 44.9497,45.5281 L 47.5281,42.9497 Z "/>
</Button>

<Button
Grid.Column="2">
<Path
Margin="5,9"
Stretch="UniformToFill"
Fill="Black"
Data="F1 M 57.9853,41.5355 L 49.0354,50.4854 C 47.9317,51.589 47,52 45,52 L 24,52 C 21.2386,52 19,49.7614 19,47 L 19,29 C 19,26.2386 21.2386,24 24,24 L 45,24 C 47,24 47.9317,24.4113 49.0354,25.5149 L 57.9853,34.4645 C 59.9379,36.4171 59.9379,39.5829 57.9853,41.5355 Z M 28.4719,42.9497 L 31.0503,45.5281 L 36,40.5784 L 40.9498,45.5281 L 43.5282,42.9497 L 38.5785,37.9999 L 43.5282,33.0502 L 40.9498,30.4718 L 36,35.4215 L 31.0503,30.4718 L 28.4719,33.0502 L 33.4216,37.9999 L 28.4719,42.9497 Z "/>
</Button>
</Grid>
</Grid>
</Window>



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



Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.