WPF-フロッピーページ

新しいiOSスタイルのフレームの実装



または単に置く-モダンUIのスタイルのフレーム。



こんにちは。 私の名前はアンドレイです。Windows10で標準のVKを使用するのは非常にうんざりしています。彼の水平方向のナビゲーションは疲れており、全体的なデザインには収まりません。 ずっと前に、私はそのようなこと、つまりiPhoneのようなスムーズなナビゲーションを実現したかったのです。 何のために? WPFでVKクライアントを作成するために。 最初に、全体像を示します。

画像






このアプローチは非常に便利であると結論付けることができます。 ページ間のDataContextはコンストラクターを介して渡されますが、より興味深いものになります。



名前空間UFC.UIから始めましょう。 各ページにいくつかのボタンがあるため、インターフェイスを作成する必要がありました。





インターフェース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; } }
      
      









画像






各ページはこのインターフェイスを継承し、非常に便利な追加を受け取ります。



部分クラス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、バインディング、コマンドなど)に使用できるようにするために行われました。

実際、その実装は次のとおりです。



インターフェイス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マークアップを見ることができます:



ローカル: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。



実際、これはそれほど重要ではありません。構築時に、OnApplyTemplate()メソッドのmainGrid要素のSizeChangedイベントに自動的にサブスクライブするためです。



 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")を使用してマークアップからすべての小さな要素へのリンクを取得する方法を思い出させてください。



アルゴリズムは次のようになりました。1つのページが表示され、次のページに移動すると、2番目のページが右端から出てきます。 最初のページは背景に移動し、アニメーションが終了すると、最初のページは2番目のページがあった右端に移動します。



したがって、frame1とframe2が位置する2つの交互のパネルを取得します。 GridNumber変数のおかげで、ページを変更するグリッドとフレームを確認します。



雑誌もここで実装されていますが、興味深いものは何もありません。 戻る(GoBack)後にのみIFloppyPageを削除する通常のリスト。



はい、もっと。 アプリケーションの寿命が始まるとすぐに、最初のページが割り当てられます。デフォルトではDefaultPageか、指定したページのいずれかになります。 その後、FloppyPagesは、IFloppyPageをNavigateおよびGoBackイベントに自動的にバインドします。 したがって、IFloppyPageの1つで別のページに移動することを決定すると、彼は監視します。



次に、FloppyPagesが作成され、最初のページが割り当てられているウィンドウを表示します。 私はすぐに、外部のデザインと丸いボタンに焦点を当てたのではなく、構造とそのさらなる使用に焦点を当てたと警告したいと思います。



部分クラスブラウザ:ウィンドウ
 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(); } } }
      
      









ウィンドウxClass UFCブラウザー
 <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" 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,23L 27,23L 27,31L 19,31L 19,23 ZM 19,34L 27,34L 27,42L 19,42L 19,34 ZM 31,23L 57,23L 57,31L 31,31L 31,23 ZM 19,45L 27,45L 27,53L 19,53L 19,45 ZM 31,34L 57,34L 57,42L 31,42L 31,34 ZM 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 ZM 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 ZM 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を簡単に使用できるようになります。







ご清聴ありがとうございました。



All Articles