開発備忘録

WPF(Xaml + C#)の話題を中心に.Net関連についてのブログです。

MediaPlayer でファイルオープン時のノイズ発生回避方法について

WPF でWAVE ファイルなどのメディアファイルを再生するとき、System.Windows.Media.MediaPlayer を使用していたのですが、複数ファイルを纏めてオープンしたときに何故か楽曲が少しだけ再生される現象が発生しました。
ここではその回避方法についてまとめています。

動作環境

実現したいこと

単純に複数の楽曲ファイルを予めロードすることで再生時のオーバーヘッドを軽減したい。
SoundPlayer クラス (System.Media) も試したのですが、「楽曲再生中に停止できない」「音量の調節機能が無い」ため、手っ取り早くMediaPlayer で実現することとなりました。

続きを読む

フォルダ内にあるMedia の再生時間を取得する(C#/WindowsAPICodePack)

特定のフォルダ内にあるメディア(MP3とかAVIとか)ファイルの再生時間を取得する方法のメモ

動作環境

WindowsAPICodePack は、以下の方法でインストールします。 www.nuget.org

以下、サンプルです。

var files = Directory.GetFiles(@"C:\work\");
foreach (var file in files)
{
    using (var shell = ShellObject.FromParsingName(file))
    {
        var property = shell.Properties.System.Media.Duration;
        if (property?.ValueAsObject != null)
        {
            var time = (ulong)property.ValueAsObject;
            Console.WriteLine($"{Path.GetFileName(file)} 再生時間[{TimeSpan.FromTicks((long)time)}]");
        }
        else
        {
            Console.WriteLine($"{Path.GetFileName(file)} 該当プロパティなし。");
        }
    }
}

ShellObject.FromParsingName でファイルの情報を取得し、Properties.System.Media.Duration で該当プロパティの値を取得します。 注意しなければならないのは、ファイルに再生時間(Duration)プロパティが無い場合は、Properties.System.Media.Duration がnull になります。 また、ファイルが破損していたりして再生時間が記録されていない場合は、Properties.System.Media.Duration.ValueAsObject がnull になります。

JR回数券と定期券の併用方法について

たまに使用する電車の乗車回数券ですが、定期券との併用方法をよく忘れるのでメモ

この記事で紹介する内容はJR西日本大阪環状線JR神戸線)でのみ使用可能であることを確認しています。
全ての線区・区間が同じ仕組みかどうかはわかりませんのでご注意ください。
※この記事は2017年7月時点のものです。

回数券はJRの券売機で購入することができる乗車券です。
詳細については以下のページを確認してください。

faq.jr-odekake.net

通常、定期券は以下の様に複数の駅間で使用していると思います。
f:id:iyemon018:20170720165411p:plain


ここで例えばB駅で乗り換えてD駅まで定期的に移動することになった場合、回数券を使用すると少し交通費を節約することができます。
f:id:iyemon018:20170720165646p:plain

イメージとしてはこんなところでしょうか。
この②の区間の回数券を購入し、通常使用している①区間の定期券と合わせて使用することが可能です。
ただし、行き(上記A駅からD駅へのルート)と帰り(上記D駅からA駅へのルート)で手順が異なるので注意してください。

■行き

1.A駅で定期券を使って乗車する。
2.D駅まで乗車する。
3.D駅の精算機でカード挿入口に"定期券"→"回数券"の順番に挿入する。
  この時、定期券にチャージ済みであってもこの方法を使用することはできます。
  ただし、支払い不要のメッセージが表示されるので素早く回数券を投入してください。
4.出場証が発行されるので通常の切符と同じように使用する。

■帰り

1.D駅で回数券を使用して乗車する。
2.A駅まで乗車する。
3.A駅の精算機でカード挿入口に"回数券"→"定期券"の順番に挿入する。
4.出場性が発行されるので通常の切符と同じように使用する。

乗車するときと生産する際の手順が異なります。
特に乗車する際は手順を間違えると回数券で支払いすることができなくなるので注意してください。

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