Storyboardを切り替える(Code Behind)
目次
- WPFのCustomControlでアニメーション-概要
- LineにLinearGradientBrushを適用したものをCustomControlとして作る
- Storyboardを作成する(XAML)
- Storyboardを切り替える(XAML)
- Storyboardを切り替える(Code Behind)←ここ
- Storyboardを作成する(Code Behind)
目的
Code Behindでプロパティによって動作するStoryboardを切り替える
作成
.cs
- 依存関係プロパティ
AnimationType
が変更されたときに使用するStoryboardを切り替える。
public bool AnimationType { get { return (bool)GetValue(AnimationTypeProperty); } set { SetValue(AnimationTypeProperty, value); } } public static readonly DependencyProperty AnimationTypeProperty = DependencyProperty.Register(nameof(AnimationType), typeof(bool), typeof(This), new PropertyMetadata(default(bool), AnimationTypeChanged)); public static void AnimationTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((This)d).UpdateStoryboard(); } private Storyboard storyBoard = default; private void UpdateStoryboard() { if (!(this.GetTemplateChild("gradientLine") is Line line)) return; storyBoard?.Remove(line); if (AnimationType) storyBoard = this.Template?.Resources["gradientAnimationTrue"] as Storyboard; else storyBoard = this.Template?.Resources["gradientAnimationFalse"] as Storyboard; storyBoard?.Begin(line, true); } public override void OnApplyTemplate() { base.OnApplyTemplate(); UpdateStoryboard(); }
Generic.xaml
<ControleTemplate.Triggers>
を削除
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GradientLineTest.Controls" > <Style TargetType="{x:Type local:GradientLineControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:GradientLineControl}"> <ControlTemplate.Resources> <Storyboard x:Key="gradientAnimationTrue" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)" From="0" To="1"/> </Storyboard> <Storyboard x:Key="gradientAnimationFalse" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[0].(GradientStop.Offset)" From="0" To="1"/> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)" From="0" To="1"/> </Storyboard> </ControlTemplate.Resources> <Line x:Name="gradientLine" X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" StrokeThickness="10" > <Line.Stroke> <LinearGradientBrush StartPoint="0.0,0.5" EndPoint="1.0,0.5"> <GradientStop Color="{Binding LeftColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="0.0"/> <GradientStop Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/> </LinearGradientBrush> </Line.Stroke> </Line> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
注意点
namescopeについて
ControlTemplate中のリソースなどの取得は通常とは異なる。これはControlTemplateは独自のnamescopeを持つため。
今回はthis.GetTemplateChild("gradientLine")
を使ってるけどthis.Template.FindName("gradientLine", this)
でもよい。
Storyboardの取得
storyboardを<Line.Resources>
に書いた場合は
line.Resources["gradientAnimationTrue"]
で取得できる。
storyboardを<Style.Resources>
に書いた場合
this.FindResource("gradientAnimationFalse")
で取得できる。
Storyboardを操作するタイミングについて
テンプレート読み込みが完了した後に行う。OnApplyTemplate
を利用すると都合が良い。
参考
Templates in WPF have a self-contained namescope. This is because templates are re-used, and any name defined in a template cannot remain unique when multiple instances of a control each instantiate its template.
OnApplyTemplate is called after the template is completely generated and attached to the logical tree.
その他
Code BehindでStoryboardを操作できるようにしておくとSetSpeedRatio
でアニメーションの動作速度を柔軟に設定できる。XAMLからでも<SetStoryboardSpeedRatio>
で設定できるけどBindingしようとすると例外'SetStoryboardSpeedRatio' の 'SpeedRatio' プロパティで 'Binding' を設定することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。'
が出る。
Storyboardを切り替える(XAML)
目次
- WPFのCustomControlでアニメーション-概要
- LineにLinearGradientBrushを適用したものをCustomControlとして作る
- Storyboardを作成する(XAML)
- Storyboardを切り替える(XAML)←ここ
- Storyboardを切り替える(Code Behind)
- Storyboardを作成する(Code Behind)
目的
プロパティによって動作するStoryboardを切り替える
作成
.cs
- 切り替えに必要な依存関係プロパティ(
AnimationType
、面倒なのでbool)を作成する
Generic.xaml側
- 必要なだけStoryboardを作成する(true時(gradientAnimationTrue),false時(gradientAnimationFalse)の二種類)
ControlTemplate.Trigger
でプロパティと対応するStoryboardを設定する
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GradientLineTest.Controls" > <Style TargetType="{x:Type local:GradientLineControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:GradientLineControl}"> <ControlTemplate.Resources> <Storyboard x:Key="gradientAnimationTrue" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)" From="0" To="1"/> </Storyboard> <Storyboard x:Key="gradientAnimationFalse" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[0].(GradientStop.Offset)" From="0" To="1"/> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)" From="0" To="1"/> </Storyboard> </ControlTemplate.Resources> <ControlTemplate.Triggers> <Trigger Property="AnimationType" Value="True"> <Trigger.EnterActions> <BeginStoryboard x:Name="typeTrueAnimation" Storyboard="{StaticResource gradientAnimationTrue}"/> </Trigger.EnterActions> <Trigger.ExitActions> <RemoveStoryboard BeginStoryboardName="typeTrueAnimation"/> </Trigger.ExitActions> </Trigger> <Trigger Property="AnimationType" Value="False"> <Trigger.EnterActions> <BeginStoryboard x:Name="typeFalseAnimation" Storyboard="{StaticResource gradientAnimationFalse}"/> </Trigger.EnterActions> <Trigger.ExitActions> <RemoveStoryboard BeginStoryboardName="typeFalseAnimation"/> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> <Line x:Name="gradientLine" X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" StrokeThickness="10" > <Line.Stroke> <LinearGradientBrush StartPoint="0.0,0.5" EndPoint="1.0,0.5"> <GradientStop Color="{Binding LeftColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="0.0"/> <GradientStop Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/> </LinearGradientBrush> </Line.Stroke> </Line> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
注意点
Storyboardを書く場所について
Storyboardは<ControlTemplate.Triggers>
から見える場所(<ControlTemplate.Resources>
か<Style.Resources>
)に書く。
<Line.Resources>
に書くと<ControleTemplate.Triggers>
から見えない(方法はあるのかもしれないけど知らない)。
Storyboard切り替え時の処理について
別のStoryboardを動かすなら元のStoryboardは止めないと混ざって期待通りの動作にならない。
(<Trigger.ExitActions>
のとこに<RemoveStoryboard>
を書いてプロパティが変わったら削除するようにしてる。止める場合は<StopStoryboard>
だけどいろいろめんどそうだったので削除を使ってる)
Storyboardを作成する(XAML)
目次
- WPFのCustomControlでアニメーション-概要
- LineにLinearGradientBrushを適用したものをCustomControlとして作る
- Storyboardを作成する(XAML)←ここ
- Storyboardを切り替える(XAML)
- Storyboardを切り替える(Code Behind)
- Storyboardを作成する(Code Behind)
ここでやること
XAML側でStoryboardを作成してGradientのoffsetを動かす
作成
.cs
何もしない
Generic.xaml
- GradientStopのoffsetを対象としたStoryboardを作る
- ロードイベントでStoryboardを開始する
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GradientLineTest.Controls" > <Style TargetType="{x:Type local:GradientLineControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:GradientLineControl}"> <Line x:Name="gradientLine" X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" StrokeThickness="10" > <Line.Resources> <Storyboard x:Key="gradientAnimation" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gradientLine" Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)" From="0" To="1"/> </Storyboard> </Line.Resources> <Line.Stroke> <LinearGradientBrush StartPoint="0.0,0.5" EndPoint="1.0,0.5"> <GradientStop Color="{Binding LeftColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="0.0"/> <GradientStop Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/> </LinearGradientBrush> </Line.Stroke> <Line.Triggers> <EventTrigger RoutedEvent="Line.Loaded"> <BeginStoryboard Storyboard="{StaticResource gradientAnimation}"/> </EventTrigger> </Line.Triggers> </Line> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
気になった点
- なんで
Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)"
みたいな書き方なの?
調べた結果
TargetProperty
にはTargetName
で指定したオブジェクトから操作目標へのパスを書く。
今回の場合、targetのlineから操作目標offsetへのパスはStroke.GradientStops[1].Offset
。これは省略形で正式に書くと(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)
。
パフォーマンスにも影響があるらしい。
※Storyboard.TargetProperty="Stroke.GradientStops[1].Offset"
でも動く
参考
You can animate a property that is a sub-property of the target object. In other words, if there's a property of the target object that's an object itself, and that object has properties, you must define a property path that explains how to step through that object-property relationship. Whenever you are specifying an object where you want to animate a sub-property, you enclose the property name in parentheses, and you specify the property in typename.propertyname format.
https://docs.microsoft.com/en-us/windows/uwp/xaml-platform/property-path-syntax
この例では、 ボタンクリック後の Rectangle の色を、 Storyboard.TargetProperty="Fill.Color" で指定しています。 これは、省略せずにきちんと書くなら、 Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" となります。 このように、TargetProperty には、階層的なプロパティの指定の仕方ができます。
https://ufcpp.net/study/dotnet/wpf_xamlani.html
リフレクションを使用することによるパフォーマンスへの影響を回避できるということです。
https://www.amazon.co.jp/dp/4798114200
その他
Storyboardを書く場所
Storyboardは<Style.Resources>
や<ControlTemplate.Resources>
に書いても動く。どこに書くのが良いのかはよくわからない。ただ上から順次解釈される(とどっかに書いてあった気がする)ため使用する場所(<Line.Triggers>
)より上の行で定義してないとエラーになる。
pathについて
Storyboard.TargetProperty
はtargetからのパスなので、例えば操作するGradientStopに名称(gs1)をつけてtargetにすればかなり短くできる。
<Line.Resources> <Storyboard x:Key="gradientAnimation" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="gs1" Storyboard.TargetProperty="Offset" From="0" To="1"/> </Storyboard>
LineにLinearGradientBrushを適用したものをCustomControlとして作る
目次
- WPFのCustomControlでアニメーション-概要
- LineにLinearGradientBrushを適用したものをCustomControlとして作る←ここ
- Storyboardを作成する(XAML)
- Storyboardを切り替える(XAML)
- Storyboardを切り替える(Code Behind)
- Storyboardを作成する(Code Behind)
ここでやること
LineにLinearGradientBrushを適用したものをCustomControlとして作る。
作成
準備
ソリューションエクスプローラを右クリック→追加→新しい項目→カスタム コントロール(WPF)
これで必要なファイル(.csとGeneric.xaml)が作成される。
.cs
線の両端(X1,X2,Y1,Y2
)とGradient用に二色(LeftColor,RightColor
)を受け取るために依存関係プロパティを作成。
※依存プロパティ作成のスニペット(propdp)が登録されているので使うと便利。
依存プロパティを作成しただけなのでコードは略
Generic.xaml
- Lineに受け取った端点(
X1,X2,Y1,Y2
)をBind。 - Line.StrokeにLinearGradientBrushを設定。
- LinearGradientBrushに受け取った二色(
LeftColor,RightColor
)をBind。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GradientLineTest.Controls" > <Style TargetType="{x:Type local:GradientLineControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:GradientLineControl}"> <Line X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" StrokeThickness="10" > <Line.Stroke> <LinearGradientBrush StartPoint="0.0,0.5" EndPoint="1.0,0.5"> <GradientStop Color="{Binding LeftColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="0.0"/> <GradientStop Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/> </LinearGradientBrush> </Line.Stroke> </Line> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
気になった点
二点
TemplateBinding
って何?- GradientStopで
TemplateBinding
ではなくBinding
つかってるのはなぜ?
調べた結果
TemplateBinding hoge
はBinding hoge, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay
の特殊ケース。
TemplateBinding
はFreezableなものには使えない。BrushはFreezableなのでGradientStopではBinding
を使用している。
他、暗黙の変換がされないなどもあるので使うときは注意。
参考
A
TemplateBinding
is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}
.
TemplateBinding - More limiting than using regular Binding
* More efficient than a Binding but it has less functionality * Only works inside a ControlTemplate's visual tree * Doesn't work with properties on Freezables * Doesn't work within a ControlTemplate's Trigger Provides a shortcut in setting properties(not as verbose),e.g. {TemplateBinding targetProperty}
https://stackoverflow.com/questions/1131222/wpf-templatebinding-vs-relativesource-templatedparent
- TemplateBinding does not support implicit type conversion in the way that a normal binding does (ie, string-to-ImageSource)
WPFのCustomControlでアニメーション-概要
題材
LineにLinearGradientBrushを適用したものをStoryboardで動かす。
これを少しずつ作っていきます。
目次
- WPFのCustomControlでアニメーション-概要←ここ
- LineにLinearGradientBrushを適用したものをCustomControlとして作る
- Storyboardを作成する(XAML)
- Storyboardを切り替える(XAML)
- Storyboardを切り替える(Code Behind)
- Storyboardを作成する(Code Behind)
背景
急にXAMLを触ることになり検索&コピペで乗り切ったがわからんことばっかなので整理する。
環境
Windows10
Microsoft Visual Studio Community 2019
.Net Core 3.0
その他
Q.これUserControlでええんちゃうの?
A.:(´・ω・`)
(´・ω:;.:…
(´:;….::;.:. :::;.. …..
Debugger.Launchでデバッガが立ち上がらなかった場合の修復方法
環境
Windows 10 version 2004
Visual Studio Professional 2015
現象
会社のPCにて。C#でDebugger.Launch()
*1を仕込んでいてもデバッガが立ち上がらなくなった。
修復方法
下記二つを実行したら立ち上がるようになった*2
- VisualStudioのツール→オプション→デバッグ→Just-In-Time→マネージドにチェック
- スタート→設定→アプリ→Visual Studio 2015→変更→修復
ついでにいつの間にか動かなくなっていた診断ツールも治った
調べたこととか
呼び出すデバッガの設定があるはずなのでまずはそれを調べることにした。レジスタに登録されていることがわかったので確認してみるとC:\windows\system32\vsjitdebugger.exe
が登録されていた。
しかし自PCに該当のファイルは存在しなかった。
vsjitdebuggerについて調べてみると個別にインストールするものではなくVisualStudioをインストールするとき一緒にインストールされるもののようだ。
レジスタが書き換わったのではなく、何かの拍子にvsjitdebuggerが削除された可能性が高いと判断。 VisualStudioの修復で復活するかもと実行したら復活した。
原因
不明。少し前にversion 1903から2004へのアップデートが行われたのでこいつが原因かも。
参考
ブックマークレットでマイクロソフト文書の日英切り替え
背景
マイクロソフトの文書はたまに日本語翻訳ミスっておりそんなときは原文見たくなります。 言語切り替えスイッチがあればよいけどない場合はURLの'ja-jp'のとこを'en-us'に書き換えてました。 毎回手打ちするのめんどくさくなったので切り替えるブックマークレット作りました。
コード
javascript:( function(){a='/ja-jp/';b='/en-us/'; if(document.location.href.indexOf(a)==-1) {location.href=document.location.href.replace(b,a);} else {location.href=document.location.href.replace(a,b);}}) (location.href);
作り方
ブックマーク作ってURLのとこに上記コードをペタリ。
使い方
該当ページで作ったブックマークをぽちりと押せば切り替わります。