やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

ViewModelのプロパティ変更をトリガーにアニメーションする方法

WPF でViewModel のプロパティが変更されたときにStoryboard を開始する方法で少し躓いたのでメモ。
開発環境は以下の通り。

ViewModel のプロパティの変更をトリガーにアニメーションするのが目的なので、まず思い浮かんだのがこれ↓

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:BindingSoureChangedAnimation"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        x:Class="BindingSoureChangedAnimation.MainWindow"
        Title="MainWindow"
        Width="525"
        Height="350"
        d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}, IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Window.Style>
        <!--
            Style のDataTrigger でViewModel のプロパティの値によってストーリーボードを開始する。
        -->
        <Style TargetType="{x:Type Window}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsAnimation}"
                             Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard Storyboard="{DynamicResource ColorChangeStoryboard}"
                                         x:Name="Begin_ColorChangeStoryboard" />
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <StopStoryboard BeginStoryboardName="Begin_ColorChangeStoryboard" />
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Style>
    <Window.Resources>
        <!-- 実行したいStoryboard  -->
        <Storyboard x:Key="ColorChangeStoryboard">
            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                          Storyboard.TargetName="rectangle">
                <EasingColorKeyFrame KeyTime="0"
                                     Value="#FF7A7AFF" />
                <EasingColorKeyFrame KeyTime="0:0:1"
                                     Value="#FFFFFFA9" />
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <CheckBox Content="アニメーション"
                  IsChecked="{Binding IsAnimation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        </CheckBox>
        <Border Grid.Row="1">
            <Rectangle x:Name="rectangle"
                       Fill="#FF7A7AFF"
                       Stroke="Black"
                       Margin="32" />
        </Border>
    </Grid>
</Window>

Window のStyle.Trigger のDataTrigger を使用するパターン。
これを実行するとこうなります。
f:id:iyemon018:20170609002314p:plain

Window のStyle からでは、Window.Resources 内のリソースにはアクセスできないとのこと…。
同様にWindow.Content のGrid のStyle でも実現はできません。

で、次の案。
Behavior のControlStoryboardAction を使用する。
ただ、このBehavior はEventTrigger になっているのでイベントの着火をトリガーとするため今回の目的には合致しません。(※)

いろいろ調べた結果、以下の方法で実現することができました。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:BindingSoureChangedAnimation"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        x:Class="BindingSoureChangedAnimation.MainWindow"
        Title="MainWindow"
        Width="525"
        Height="350"
        d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}, IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Window.Resources>
        <!-- 実行したいStoryboard  -->
        <Storyboard x:Key="ColorChangeStoryboard"
                    RepeatBehavior="Forever">
            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                          Storyboard.TargetName="rectangle">
                <EasingColorKeyFrame KeyTime="0"
                                     Value="#FF7A7AFF" />
                <EasingColorKeyFrame KeyTime="0:0:1"
                                     Value="#FFFFFFA9" />
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <i:Interaction.Triggers>
        <!--
            IsAnimation プロパティの値が"True" ならStoryboard を開始する。
            "True" 以外の値ならStoryboard を停止する。
        -->
        <ei:DataTrigger Binding="{Binding IsAnimation, Mode=OneWay}"
                        Value="True">
            <ei:ControlStoryboardAction Storyboard="{StaticResource ColorChangeStoryboard}" />
        </ei:DataTrigger>
        <ei:DataTrigger Binding="{Binding IsAnimation, Mode=OneWay}"
                        Value="True"
                        Comparison="NotEqual">
            <ei:ControlStoryboardAction Storyboard="{StaticResource ColorChangeStoryboard}"
                                        ControlStoryboardOption="Stop" />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <CheckBox Content="アニメーション"
                  IsChecked="{Binding IsAnimation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Border Grid.Row="1">
            <Rectangle x:Name="rectangle"
                       Fill="#FF7A7AFF"
                       Stroke="Black"
                       Margin="32" />
        </Border>
    </Grid>
</Window>

変更点はここ

    <i:Interaction.Triggers>
        <!--
            IsAnimation プロパティの値が"True" ならStoryboard を開始する。
            "True" 以外の値ならStoryboard を停止する。
        -->
        <ei:DataTrigger Binding="{Binding IsAnimation, Mode=OneWay}"
                        Value="True">
            <ei:ControlStoryboardAction Storyboard="{StaticResource ColorChangeStoryboard}" />
        </ei:DataTrigger>
        <ei:DataTrigger Binding="{Binding IsAnimation, Mode=OneWay}"
                        Value="True"
                        Comparison="NotEqual">
            <ei:ControlStoryboardAction Storyboard="{StaticResource ColorChangeStoryboard}"
                                        ControlStoryboardOption="Stop" />
        </ei:DataTrigger>
    </i:Interaction.Triggers>

ControlStoryboardAction 使ってるんですが、そのトリガーに"ei:DataTrigger" を使用しています。
通常の"DataTrigger" とは違って"Microsoft.Expression.Interactivity.Core" 名前空間にいるやつです。
こいつを探し出すのが苦労したorz

通常、Blend からBehavior をドラッグ・アンド・ドロップで配置するとトリガーは、"EventTrigger" になります。
これを"ei:DataTrigger" に変更するには以下の手順を実行します。

1.Behavior を配置する。

2.EventTrigger をDataTrigger に変更する。
  プロパティ ウィンドウの[トリガー] - [新規] をクリックします。
f:id:iyemon018:20170609004623p:plain

  一覧から"DataTrigger" を選択して[OK] をクリックします。
f:id:iyemon018:20170609004816p:plain

3.トリガーの値を設定する。
  プロパティ ウィンドウの[トリガー] "TriggerType" が"DataTrigger" に変わっていることを確認してViewModel のプロパティの値をバインドする。
f:id:iyemon018:20170609004947p:plain

これを探し出すのに一番時間がかかりました。
ちなみにControlStoryboardAction は、Play とStop それぞれのアクションを設定しないとStoryboard を開始、終了することができません。
また、値の比較種別として"Equal", "NotEqual" 以外にも"LessThan (未満)", "GreaterThan (~より大きい)" 等があるので、ViewModel のプロパティがある数値以上の場合にアニメーションを制御する といったこともできそうです。(未確認)


今回使用したソースはこちら↓↓↓
github.com

(WPF) ウィンドウのバウンド エフェクトを無効化する方法

Windows10 でスクロールバーが表示されたListBox をタッチ操作したときにバウンドするようなUI 表現を無効化する方法についてメモします。

タブレットでWindows10 を使用している際に、エクスプローラーなどで一覧表示している際に、領域外までスクロールするとウィンドウが少しだけ移動します。
イメージ的にはこんな感じ↓↓↓
f:id:iyemon018:20170605132510p:plain

この動作はWindow 内にListBox やDataGrid などのリスト系コントロールがある場合に発生します。
地味に厄介なのが、最大化表示でウィンドウ枠を非表示にしても発生するという点です。
デジタルサイネージのようにクライアント領域をフルに使用している場合にこの動作が発生すると、少しだけ背景(デスクトップなど)が見えてしまって非常に格好悪いです。

この動作は、.NET Framework 4.0 で追加された"ManipulationBoundaryFeedback"イベントで無効化することができます。


以下の例では、ListBox のManipulationBoundaryFeedback イベントを追加しています。

private void ListBox_ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
    e.Handled = true;
}

これで領域外でスクロールしてもバウンドしなくなりました。
他にも、"e.BoundaryFeedback" プロパティでタッチ操作の移動ベクトルや移動対象のオブジェクトを取得することができます。
今回の例ではイベントで対処していますが、アプリケーションに組み込むのであればBehavior にしたほうがいいでしょう。

以上、小ネタでした。

Windows10 + WPF でタッチ フィードバックを無効化する(※)

Windows10 上のWPF アプリケーションでタッチ フィードバックを無効化する方法にいてメモ

※まず最初に、この記事で紹介する方法はあるパターン(後述)に対応できていません。
 そのパターンでの対応方法が見つかれば追記 or あらたに記事を書きます。

タッチ フィードバックとは?

Windows8 以降(?)に登場したタッチパネル向けの視覚的表現です。
下の白い円状のインジケータがそれです。
f:id:iyemon018:20170525223659p:plain

このタッチ フィードバックは、以下の設定で表現のレベルを変更することができます。
[Windowsの設定] - [簡単操作] - [その他のオプション] - [タッチ フィードバック]
f:id:iyemon018:20170525224213p:plain

or
[コントロールパネル] - [ペンとタッチ] - [タッチ]
f:id:iyemon018:20170525224224p:plain

この設定は同期しているのでどちらかを変更すればもう片方も変わります。

無効化するには

上記の設定をOFFにすればデスクトップやエクスプローラ、ブラウザなどを操作している感じでは、無効化することができます。
ただし、独自に作成したWPFなどのアプリケーションは、この設定の影響を受けないようです。
Visual Studioもこの影響を受けません。
(UWPならこの設定の影響を受けるのかもしれませんが未検証です。)

ということでタッチ フィードバックを無効化する方法について色々調べてみた結果、完成したコードがこちら↓↓↓

続きを読む

Windows10でエッジ スワイプUI を無効化する方法

teratail にも質問したんですが、
teratail.com
結局回答が無かったので自力で調べました。

エッジ スワイプってなぁにって言うと、タブレットなどでディスプレイの端から中央に向かってスワイプする操作のことです。
以下のページでアクションセンターを表示する例が記載されているのでこちらを見るとわかりやすいかなと。
surface.jp.net

このエッジ スワイプによって表示されるシステムUI のことを"エッジ スワイプUI"と呼ぶらしい(?)です。

今回、タブレットのようにタッチ操作が可能なアプリケーションを作っていました。
フルスクリーンで常に最前面で表示されてほしかったのですが、上記のエッジ スワイプUI が表示されてしまってどうしようかな~?と悩んだ結果質問を投げてみました。

とりあえず、色々調べた結果、以下の方法でエッジ スワイプUI を無効化することができることが分かりました。
winintro.com

一つ注意点として、通常上記の値はレジストリ上にはありません。
多分 HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows に"EdgeUI" が無いと思います。(私の開発PCではありませんでした。)
この値がなければエッジ スワイプUI は有効になります。
つまり、無効化するにはレジストリの値を新規追加する必要があります。
また、レジストリの値の書き換えなので、OSを再起動するまで反映されません。
もとに戻す場合も同様です。

(2017年8月28日追記)
エッジ スワイプUIは、どうやらWindows10 のVer.1511 では無効化できないようです。
現在のところ、Ver.1607/1703 では無効化が可能であることを確認しています。

Setupインストーラーでインストール後に再起動する方法

デスクトップアプリでインストーラーからインストールが完了した後にOSを再起動する方法についてメモします。

動作環境

下準備

  1. Visual Studio拡張機能であるインストーラープロジェクトを追加します。Microsoft Visual Studio 2015 Installer Projects - Visual Studio Marketplace
  2. MSIデータ テーブル編集ツールのOrca をインストールします。

Orcaについては、以下を参照してください。
https://support.microsoft.com/ja-jp/help/255905/how-to-use-the-orca-database-editor-to-edit-windows-installer-files

OrcaWindows SDK に含まれています。が、Windows 7 向けのSDK にしかインストールされていないので別途Windows 7 SDK をインストールする必要があります。
Download Microsoft Windows SDK for Windows 7 and .NET Framework 4 from Official Microsoft Download Center

OrcaSDK の以下のパスに保存されています。
(環境によってはパスが違うかもしれません。)
%Program Files%\Microsoft SDKs\Windows\v7.0A\Bin\Orca.Msi

これをインストールすれば準備OKです。

再起動の設定

まずは、通常通りインストーラー プロジェクトをビルドして、"Setup.exe"と"*.msi"を作成します。
次に、Orca を起動して、作成したMSI ファイルを開きます。
画面左部の[Table]から"Property"を選択すると、MSI ファイルのプロパティの一覧が表示されます。
ここで右クリック - [Add Row] を選択し、以下の内容を入力します。
f:id:iyemon018:20170420174328j:plain

f:id:iyemon018:20170420174428j:plain

"REBOOT"プロパティの値については以下のページを参照してください。
REBOOT property (Windows)

後はMSI ファイルを上書きすれば完了です。

インストールが完了すると、以下のメッセージが表示され、[はい]ボタンをクリックするとOSが再起動されるようになります。
f:id:iyemon018:20170420174841j:plain