trsing’s diary

勉強、読んだ本、仕事で調べたこととかのメモ。

Storyboardを切り替える(Code Behind)

目次

  1. WPFのCustomControlでアニメーション-概要
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る
  3. Storyboardを作成する(XAML)
  4. Storyboardを切り替える(XAML)
  5. Storyboardを切り替える(Code Behind)←ここ
  6. 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.

https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.gettemplatechild?redirectedfrom=MSDN&view=net-5.0#System_Windows_FrameworkElement_GetTemplateChild_System_String_

OnApplyTemplate is called after the template is completely generated and attached to the logical tree.

https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.applytemplate?view=net-5.0#System_Windows_FrameworkElement_ApplyTemplate

その他

Code BehindでStoryboardを操作できるようにしておくとSetSpeedRatioでアニメーションの動作速度を柔軟に設定できる。XAMLからでも<SetStoryboardSpeedRatio>で設定できるけどBindingしようとすると例外'SetStoryboardSpeedRatio' の 'SpeedRatio' プロパティで 'Binding' を設定することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。'が出る。

Storyboardを切り替える(XAML)

目次

  1. WPFのCustomControlでアニメーション-概要
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る
  3. Storyboardを作成する(XAML)
  4. Storyboardを切り替える(XAML)←ここ
  5. Storyboardを切り替える(Code Behind)
  6. Storyboardを作成する(Code Behind)

目的

プロパティによって動作するStoryboardを切り替える

f:id:trsing:20210228223449g:plain

作成

.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)

目次

  1. WPFのCustomControlでアニメーション-概要
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る
  3. Storyboardを作成する(XAML)←ここ
  4. Storyboardを切り替える(XAML)
  5. Storyboardを切り替える(Code Behind)
  6. Storyboardを作成する(Code Behind)

ここでやること

XAML側でStoryboardを作成してGradientのoffsetを動かす

f:id:trsing:20210228223254g:plain

作成

.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として作る

目次

  1. WPFのCustomControlでアニメーション-概要
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る←ここ
  3. Storyboardを作成する(XAML)
  4. Storyboardを切り替える(XAML)
  5. Storyboardを切り替える(Code Behind)
  6. Storyboardを作成する(Code Behind)

ここでやること

LineにLinearGradientBrushを適用したものをCustomControlとして作る。

f:id:trsing:20210228180614p:plain

作成

準備

ソリューションエクスプローラを右クリック→追加→新しい項目→カスタム コントロール(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 hogeBinding 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}.

https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/templatebinding-markup-extension?view=netframeworkdesktop-4.8

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)

https://stackoverflow.com/questions/22134281/templatebinding-fails-when-the-target-is-an-imagebrush-imagesource

WPFのCustomControlでアニメーション-概要

題材

LineにLinearGradientBrushを適用したものをStoryboardで動かす。

f:id:trsing:20210228172431g:plain

これを少しずつ作っていきます。

目次

  1. WPFのCustomControlでアニメーション-概要←ここ
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る
  3. Storyboardを作成する(XAML)
  4. Storyboardを切り替える(XAML)
  5. Storyboardを切り替える(Code Behind)
  6. 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へのアップデートが行われたのでこいつが原因かも。

参考

stackoverflow.com

stackoverflow.com

*1:デバッガーを起動し、プロセスにアタッチするメソッド

*2:一つ目は不要かも

ブックマークレットでマイクロソフト文書の日英切り替え

背景

マイクロソフトの文書はたまに日本語翻訳ミスっておりそんなときは原文見たくなります。 言語切り替えスイッチがあればよいけどない場合は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);

参考
www.abilitydesign.net

作り方

ブックマーク作ってURLのとこに上記コードをペタリ。

使い方

該当ページで作ったブックマークをぽちりと押せば切り替わります。

f:id:trsing:20201227105645p:plain

f:id:trsing:20201227105801p:plain