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

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

Jenkins でxUnit + OpenCover を使ったカバレッジ集計・レポート表示方法について

少し前にJenkins でxUnit を使ったユニットテストの実行方法を試しました。
iyemon018.hatenablog.com

今回は、Jenkins でxUnit でユニットテストを実行し、OpenCover を使ってカバレッジを集計・プロジェクトページに表示する方法についてメモします。

続きを読む

TFS でTF14061 が表示されたときの対処方法

TFS 使っているときにTF14061 なるエラーメッセージが表示されたときの事象と対処方法メモです。

現象

Visual Studio でTFS と接続しているときに、チーム エクスプローラー上に"予期しないファイルの終わりを検出しました。"というメッセージが表示される。
この状態でチェックインしても同じメッセージが表示されてしまい、チェックインすることができない。
ソース管理エクスプローラーを表示しようとするとマップが解除されてしまっていて表示されない。
このときに"TF14061" メッセージが表示される。

原因

TFS のキャッシュにゴミデータが含まれてしまっているため。
異なるバージョンのTFS に接続してOS を再起動したときに発生したので、TFS バージョンの混在が原因かも?

対処方法

一度Visual Studio を終了して"%USERPROFILE%\AppData\Local\Microsoft\Team Foundation\6.0" にある"Cache" フォルダを削除する。
Visual Studio を再起動するとチェックイン出来るようになる。

WPF でVisualTree のヒット テストを実行する

VisualTreeHelper を使用すると特定のコントロールのVisualTree 要素を検索したりできることは知っていたのですが、今まで使用する機会は殆どありませんでした。
今回使用したときに躓いた箇所も含めて、その使用方法をメモします。

以下、動作環境です。

VisualTreeHelper とは

WPF (と言うかXAML) には"LogicalTree" と"VisualTree" の2種類の要素ツリーが存在します。

  • LogicalTree は、論理ツリー つまりXAML で定義した内容で表されるツリーです。
  • VisualTree は、テンプレートなどの内容も含めた、その名の通りビジュアライズされた要素のツリーです。

"VisualTreeHelper" は、このVisualTree 上の要素を検索したりヒット テストを実施するためのヘルパー クラスです。

続きを読む

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 にしたほうがいいでしょう。

以上、小ネタでした。