WPF で画面遷移する方法 4
(2017/05/19 16:13:11 created.)
(2017/05/24 16:46:50 modified.)
前回は TabControl によって画面遷移をおこないましたが、遷移アニメーションがなくてさみしくなってしまいました。ここでは TabControl によってアニメーションしながら画面遷移するようにします。
わざわざ記事を分けてしまいましたが、実は結構簡単。一番最初に紹介した TransitionPanel コントロールを使います。もう忘れてしまっていると思うのでコードを再掲します。
TransitionPanel.xaml
<UserControl x:Class="TabSample.Views.TransitionPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignWidth="400" d:DesignHeight="600"
x:Name="root">
<Grid>
<ContentControl Content="{Binding ContentA, ElementName=root}">
<ContentControl.RenderTransform>
<TranslateTransform X="{Binding OffsetXA, ElementName=root}" Y="{Binding OffsetYA, ElementName=root}" />
</ContentControl.RenderTransform>
</ContentControl>
<ContentControl Content="{Binding ContentB, ElementName=root}">
<ContentControl.RenderTransform>
<TranslateTransform X="{Binding OffsetXB, ElementName=root}" Y="{Binding OffsetYB, ElementName=root}" />
</ContentControl.RenderTransform>
</ContentControl>
</Grid>
</UserControl>
TransitionPanel.xaml.cs
namespace TabSample.Views
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
/// <summary>
/// TransitionPanel.xaml の相互作用ロジック
/// </summary>
public partial class TransitionPanel : UserControl
{
/// <summary>
/// 新しいインスタンスを生成します。
/// </summary>
public TransitionPanel()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
/// <summary>
/// アニメーションの方向を表します。
/// </summary>
public enum TransitDirections
{
/// <summary>
/// 左へ移動します。
/// </summary>
ToLeft,
/// <summary>
/// 右へ移動します。
/// </summary>
ToRight,
}
/// <summary>
/// 遷移状態を表します。
/// </summary>
public enum TransitionStates
{
/// <summary>
/// A が表示されている状態を表します。
/// </summary>
DisplayA,
/// <summary>
/// B が表示されている状態を表します。
/// </summary>
DisplayB,
}
#region Content 依存関係プロパティ
/// <summary>
/// Content 依存関係プロパティを定義し直します。
/// </summary>
public static readonly new DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(TransitionPanel), new UIPropertyMetadata(null, OnConentPropertyChanged));
/// <summary>
/// コンテンツを取得または設定します。
/// </summary>
public new object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
#endregion Content 依存関係プロパティ
#region ContentA 依存関係プロパティ
/// <summary>
/// ContentA 依存関係プロパティのキーを定義します。
/// </summary>
private static readonly DependencyPropertyKey ContentAPropertyKey = DependencyProperty.RegisterReadOnly("ContentA", typeof(object), typeof(TransitionPanel), new UIPropertyMetadata(null));
/// <summary>
/// ContentA 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty ContentAProperty = ContentAPropertyKey.DependencyProperty;
/// <summary>
/// コンテンツのためのバッファ A を取得します。
/// </summary>
public object ContentA
{
get { return GetValue(ContentAProperty); }
private set { SetValue(ContentAPropertyKey, value); }
}
#endregion ContentA 依存関係プロパティ
#region ContentB 依存関係プロパティ
/// <summary>
/// ContentB 依存関係プロパティのキーを定義します。
/// </summary>
private static readonly DependencyPropertyKey ContentBPropertyKey = DependencyProperty.RegisterReadOnly("ContentB", typeof(object), typeof(TransitionPanel), new UIPropertyMetadata(null));
/// <summary>
/// ContentB 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty ContentBProperty = ContentBPropertyKey.DependencyProperty;
/// <summary>
/// コンテンツのためのバッファ B を取得します。
/// </summary>
public object ContentB
{
get { return GetValue(ContentBProperty); }
private set { SetValue(ContentBPropertyKey, value); }
}
#endregion ContentB 依存関係プロパティ
#region State 依存関係プロパティ
/// <summary>
/// State 依存関係プロパティのキーを定義します。
/// </summary>
private static readonly DependencyPropertyKey StatePropertyKey = DependencyProperty.RegisterReadOnly("State", typeof(TransitionStates), typeof(TransitionPanel), new UIPropertyMetadata(TransitionStates.DisplayB));
/// <summary>
/// State 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty StateProperty = StatePropertyKey.DependencyProperty;
/// <summary>
/// 遷移状態を取得します。
/// </summary>
public TransitionStates State
{
get { return (TransitionStates)GetValue(StateProperty); }
private set { SetValue(StatePropertyKey, value); }
}
#endregion State 依存関係プロパティ
#region TransitDirection 依存関係プロパティ
/// <summary>
/// TransitDirection 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty TransitDirectionProperty = DependencyProperty.Register("TransitDirection", typeof(TransitDirections), typeof(TransitionPanel), new UIPropertyMetadata(TransitDirections.ToLeft));
/// <summary>
/// 画面遷移方向を取得または設定します。
/// </summary>
public TransitDirections TransitDirection
{
get { return (TransitDirections)GetValue(TransitDirectionProperty); }
set { SetValue(TransitDirectionProperty, value); }
}
#endregion TransitDirection 依存関係プロパティ
#region OffsetXA 依存関係プロパティ
/// <summary>
/// OffsetXA 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty OffsetXAProperty = DependencyProperty.Register("OffsetXA", typeof(double), typeof(TransitionPanel), new UIPropertyMetadata(0.0));
/// <summary>
/// コンテンツのためのバッファ A の水平方向オフセットを取得または設定します。
/// </summary>
public double OffsetXA
{
get { return (double)GetValue(OffsetXAProperty); }
set { SetValue(OffsetXAProperty, value); }
}
#endregion OffsetXA 依存関係プロパティ
#region OffsetYA 依存関係プロパティ
/// <summary>
/// OffsetYA 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty OffsetYAProperty = DependencyProperty.Register("OffsetYA", typeof(double), typeof(TransitionPanel), new UIPropertyMetadata(0.0));
/// <summary>
/// コンテンツのためのバッファ A の垂直方向オフセットを取得または設定します。
/// </summary>
public double OffsetYA
{
get { return (double)GetValue(OffsetYAProperty); }
set { SetValue(OffsetYAProperty, value); }
}
#endregion OffsetYA 依存関係プロパティ
#region OffsetXB 依存関係プロパティ
/// <summary>
/// OffsetXB 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty OffsetXBProperty = DependencyProperty.Register("OffsetXB", typeof(double), typeof(TransitionPanel), new UIPropertyMetadata(0.0));
/// <summary>
/// コンテンツのためのバッファ B の水平方向オフセットを取得または設定します。
/// </summary>
public double OffsetXB
{
get { return (double)GetValue(OffsetXBProperty); }
set { SetValue(OffsetXBProperty, value); }
}
#endregion OffsetXB 依存関係プロパティ
#region OffsetYB 依存関係プロパティ
/// <summary>
/// OffsetYB 依存関係プロパティを定義します。
/// </summary>
public static readonly DependencyProperty OffsetYBProperty = DependencyProperty.Register("OffsetYB", typeof(double), typeof(TransitionPanel), new UIPropertyMetadata(0.0));
/// <summary>
/// コンテンツのためのバッファ B の垂直方向オフセットを取得または設定します。
/// </summary>
public double OffsetYB
{
get { return (double)GetValue(OffsetYBProperty); }
set { SetValue(OffsetYBProperty, value); }
}
#endregion OffsetYB 依存関係プロパティ
#region イベントハンドラ
/// <summary>
/// Load イベントハンドラ
/// </summary>
/// <param name="sender">イベント発行元</param>
/// <param name="e">イベント引数</param>
private void OnLoaded(object sender, RoutedEventArgs e)
{
var storyboard = new Storyboard();
storyboard.Children = new TimelineCollection()
{
CreateMoveAnimation(TimeZero, TimeZero, this.HorizontalOffset, "OffsetXB"),
};
storyboard.Begin();
}
/// <summary>
/// Content 依存関係プロパティ変更イベントハンドラ
/// </summary>
/// <param name="d">イベント発行元</param>
/// <param name="e">イベント引数</param>
private static void OnConentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as TransitionPanel;
if (control.IsInitialized) control.SwapDisplay();
}
#endregion イベントハンドラ
#region ヘルパ
/// <summary>
/// コンテンツを入れ替えます。
/// </summary>
private void SwapDisplay()
{
if (this.State == TransitionStates.DisplayA)
{
this.ContentB = this.Content;
this.State = TransitionStates.DisplayB;
}
else
{
this.ContentA = this.Content;
this.State = TransitionStates.DisplayA;
}
if ((this.ContentA != null) && (this.ContentB != null))
StartAnimation();
}
/// <summary>
/// 画面遷移を開始します。
/// </summary>
private void StartAnimation()
{
var storyboard = this.State == TransitionStates.DisplayA ? CreateAnimationBtoA(this.TransitDirection) : CreateAnimationAtoB(this.TransitDirection);
storyboard.Begin();
}
/// <summary>
/// ContentB から ContentA へ遷移するためのストーリーボードを生成します。
/// </summary>
/// <param name="direction">遷移する方向を指定します。</param>
/// <returns>生成したストーリーボードを返します。</returns>
private Storyboard CreateAnimationBtoA(TransitDirections direction)
{
var storyboard = new Storyboard();
storyboard.Children = direction == TransitDirections.ToLeft ?
new TimelineCollection()
{
CreateMoveAnimation(TimeZero, TimeZero, this.HorizontalOffset, "OffsetXA"),
CreateMoveAnimation(TimeZero, AnimationTime, 0, "OffsetXA"),
CreateMoveAnimation(TimeZero, AnimationTime, -this.HorizontalOffset, "OffsetXB"),
} :
new TimelineCollection()
{
CreateMoveAnimation(TimeZero, TimeZero, -this.HorizontalOffset, "OffsetXA"),
CreateMoveAnimation(TimeZero, AnimationTime, 0, "OffsetXA"),
CreateMoveAnimation(TimeZero, AnimationTime, this.HorizontalOffset, "OffsetXB"),
};
return storyboard;
}
/// <summary>
/// ContentA から ContentB へ遷移するためのストーリーボードを生成します。
/// </summary>
/// <param name="direction">遷移する方向を指定します。</param>
/// <returns>生成したストーリーボードを返します。</returns>
private Storyboard CreateAnimationAtoB(TransitDirections direction)
{
var storyboard = new Storyboard();
storyboard.Children = direction == TransitDirections.ToLeft ?
new TimelineCollection()
{
CreateMoveAnimation(TimeZero, TimeZero, this.HorizontalOffset, "OffsetXB"),
CreateMoveAnimation(TimeZero, AnimationTime, 0, "OffsetXB"),
CreateMoveAnimation(TimeZero, AnimationTime, -this.HorizontalOffset, "OffsetXA"),
} :
new TimelineCollection()
{
CreateMoveAnimation(TimeZero, TimeZero, -this.HorizontalOffset, "OffsetXB"),
CreateMoveAnimation(TimeZero, AnimationTime, 0, "OffsetXB"),
CreateMoveAnimation(TimeZero, AnimationTime, this.HorizontalOffset, "OffsetXA"),
};
return storyboard;
}
/// <summary>
/// Double 型のプロパティに対するアニメーションを生成します。
/// </summary>
/// <param name="beginTime">アニメーションの開始時間を指定します。</param>
/// <param name="duration">アニメーションの実行時間を指定します。</param>
/// <param name="to">プロパティ値の最終値を指定します。</param>
/// <param name="targetPropertyName">対象とするプロパティ名を指定します。</param>
/// <returns>Storyboard の添付プロパティを設定したアニメーションを返します。</returns>
private DoubleAnimation CreateMoveAnimation(TimeSpan beginTime, TimeSpan duration, double to, string targetPropertyName)
{
var animation = new DoubleAnimation()
{
To = to,
BeginTime = beginTime,
Duration = new Duration(duration),
AccelerationRatio = 0.3,
DecelerationRatio = 0.3,
};
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath(targetPropertyName));
return animation;
}
#endregion ヘルパ
#region private フィールド
/// <summary>
/// 時刻ゼロ
/// </summary>
private static readonly TimeSpan TimeZero = TimeSpan.FromMilliseconds(0);
/// <summary>
/// アニメーション時間
/// </summary>
private static readonly TimeSpan AnimationTime = TimeSpan.FromMilliseconds(500);
/// <summary>
/// 水平方向の遷移量
/// </summary>
private double HorizontalOffset { get { return this.ActualWidth + 10; } }
#endregion private フィールド
}
}
Content プロパティの変化を捕捉して TranslateTransform によって 2 つのコンテンツを水平移動するコントロールでしたね。これを TabControl で使います。
MainView.xaml
<YK:Window x:Class="TabSample.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
xmlns:vw="clr-namespace:TabSample.Views"
Title="MainView"
Width="300" Height="200">
<DockPanel>
<ComboBox x:Name="combobox" DockPanel.Dock="Bottom" SelectedIndex="0">
<ComboBoxItem>Content01</ComboBoxItem>
<ComboBoxItem>Content02</ComboBoxItem>
<ComboBoxItem>Content03</ComboBoxItem>
</ComboBox>
<TabControl SelectedIndex="{Binding SelectedIndex, ElementName=combobox}">
<TabControl.Template>
<ControlTemplate TargetType="{x:Type TabControl}">
<vw:TransitionPanel Content="{Binding SelectedContent, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</TabControl.Template>
<TabItem>
<Grid DataContext="{Binding Content01ViewModel}">
<CheckBox Content="{Binding Caption}" />
</Grid>
</TabItem>
<TabItem>
<Grid DataContext="{Binding Content02ViewModel}">
<TextBlock Text="{Binding Caption}" />
</Grid>
</TabItem>
<TabItem>
<Grid DataContext="{Binding Content03ViewModel}">
<TextBlock Text="{Binding Caption}" />
</Grid>
</TabItem>
</TabControl>
</DockPanel>
</YK:Window>
そう、TabControl でコンテンツを表示するときに ContentPresenter を指定していましたね。これを TransitionPanel に差し替えるだけです。ただし、SelectedContent で渡ってくる要素は TabItem ではなく、その直下の要素になるため、DataContext を切り替える場合はこの例のように TabItem 直下に Grid など親要素となるパネルを置き、その DataContext を明確に指定するようにします。
一番最初に紹介した外観とまったく同じものができあがりました。違いは DataTemplate による View と ViewModel の紐付けをしていないところです。また、だからといって App クラスにややこしい GetView() などというメソッドも作っていません。単に MainView.xaml に各コンテンツを配置しているだけでわかりやすくなりました。
Tweet
<< 古い記事へ WPF で画面遷移する方法 3 |
新しい記事へ >> スタイルシート更新しました |