ラベル WPF の投稿を表示しています。 すべての投稿を表示
ラベル WPF の投稿を表示しています。 すべての投稿を表示

2020年5月4日月曜日

WPFでViewModelからWindowをCloseさせる

View層からのCloseは、this.Close()とかでOKだけど、WPFなのでViewModel層から画面を閉じたいことって結構あります。
今回は「Windowがアクティブじゃない場合」についても考慮しています。

1.引数としてWindowを受け取る

コマンドのバインド時にCommandParameterにWindowを渡します。
例)画面Load時のイベントにWindowを渡す場合

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Loaded">
  <i:InvokeCommandAction
      Command="{Binding LoadedCommand}"
      CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>


ViewModelは
private void Loaded(Window window)
{
  ・・・
  window.close()
}


のように画面を閉じることができます。
個人的にはViewModelでControl触るのはあまり好きじゃないなぁ

2.Closeメッセージを送る

こっちの方がMVVMぽいかな。Livetを使用しています。
XAML側にCLOSEメッセージ受信用のトリガーを書きます

<i:Interaction.Triggers>
  <l:InteractionMessageTrigger MessageKey="Close" Messenger="{Binding Messenger, Mode=OneWay}">
  <l:WindowInteractionMessageAction InvokeActionOnlyWhenWindowIsActive="False"/>
  </l:InteractionMessageTrigger>
</i:Interaction.Triggers>

private void Loaded(Window window)
{
  ・・・
  Messenger.Raise(new WindowActionMessage(WindowAction.Close, "Close"));
}
※メッセージを使用する場合、Windowがアクティブではない場合、デフォルトでは処理が動きません。
通常の「閉じるボタンクリック」などでは問題ないですが「処理が終わったら画面を閉じる」のような処理の場合は、Windowが前面に出ていない可能性が結構あると思います。
InvokeActionOnlyWhenWindowIsActive="False"
を設定することで、アクティブ状態でなくてもメッセージを受け取ることができます。

2020年5月3日日曜日

C#からFFmpegを制御する

前回、FFmpegからRtmp配信をする方法について記載しました。

コマンドプロンプトからFFmpegを使用しての動画の変換やダウンロードであれば、あまり気にする必要ないかもしれませんがWPFなどから起動する場合はProcessクラスを使用します。

1.プログラムからFFmpegを起動する

まずは基本形
public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "FFmpegのオプション指定";
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();
}

こんな感じ。
CreateNoWindow = true
を指定すると、ウィンドウが表示されません。

2.FFmpegを停止する

放っておいて停止するような場合は構いませんが、ライブ配信を行うような場合は、終了処理が必要となります。

public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "FFmpegのオプション指定";
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();
}

今回はRedirectStandardInput = trueを指定しています。
これで、プロセスに対してコマンドの送付が可能となります。
FFmpegは「Q」キーで停止可能となっています。
なので、停止指示は「Q」を送付すればOK

public void StopFfmpeg()
{
    StreamWriter inputWriter = proc.StandardInput;
    inputWriter.WriteLine("q");
    rtmpProc.WaitForExit(3000);
}


3.標準入力を使用して入力データを送る

今度はWEBカメラからの映像の取り込み動画に変換します。

public void RunFfmpeg()
{
    var proc = new Process();
    proc.StartInfo.FileName = "FFmpeg.exeのパス";
    proc.StartInfo.Arguments = "-i - -f image2pipe output.mp4";
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.CreateNoWindow = true; // ウィンドウ表示しない
    proc.StartInfo.UseShellExecute = false;
    proc.Start();

}

Arguments の「-i -」を指定することで、StandardInputからの入力を受け付けるようになります。
また、フォーマットでは「image2pipe 」を指定します。

次にカメラからの画像の取り込みについて。
こちらのサイトではWEBカメラを使用した画像の取り込みを行っています。
C#によるUSBカメラ操作(WPF編)

画像の取り込みには、Accord.Video.DirectShowのVideoCaptureDeviceを使用します。

public event NewFrameEventHandler NewFrameGot = delegate { };

public void StartCamera()
{
    var device = new VideoCaptureDevice("カメラ名");
    device.NewFrame += NewFrameGot;
    device.Start();
}

こんな感じで、NewFrameイベントを使用します。
// コンストラクタで指定
NewFrameGot += CamDeviceCtrlNewFrameGot;

private void CamDeviceCtrlNewFrameGot(object sender, NewFrameEventArgs eventArgs)
{
    eventArgs.Frame.Save(proc.StandardInput.BaseStream, ImageFormat.Bmp);
}

WPFで動画配信の再生(Vlc.DotNet.Wpf)

この記事ではRTMPの受信、再生をする方法として記載していますが、RTMPだけでなくHLSなどライブストリーム配信について同じ方法で対応可能かなと思います。


前回記事ではFFMpegを使用したRTMPの配信方法について記載しました。

WPFだろうが何だろうが、C#ではProcessとしてFFmpegを起動してあげれば配信可能なので、簡単かなと思います。

問題なのは再生の方かな。

1.再生用のWebページを作って、そのページをWPFで開く

サーバに配信用のPlayerとHTMLを置いて、そのページをWebBrowserコントロールで表示しようと思いました。
これなら、WPF側では制御いらないのでいいのかなと・・・

PlayerとしてはHTML5のvideoタグの利用をする方法と、FlowPlayerを利用する方法を試してみました。
WebBrowserコントロールはそのままではHTML5が表示できないので、レジストリを修正。
WPFのWebBrowserでHTML5を表示する

単体では動かせるけど、webBrowserコントロールで表示するとなると、動いてくれません。
やはり、javaやらflashなどを含めたページだから厳しいのかな・・・?

2.VlcControlの使用

結局うまくいったのは、Vlc.DotNet.Wpfを使用する方法です。

こちらのサイトを参考に導入しました。



必要なものは以下の4つ。
NuGetから取得できます。

  • VideoLAN.LibVLC.Windows
  • Vlc.DotNet.Core
  • Vlc.DotNet.Core.Interops
  • Vlc.DotNet.Wpf


VlcPlayerはXAMLのコードビハインドで以下のように書くだけ。

VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));
string StreamParams = ":network-caching=2000";

var player = new VlcControl();
container.Content = player;
player.SourceProvider.CreatePlayer(VlcLibDirectory);
player.SourceProvider.MediaPlayer.Play(new Uri(RTMPのアドレス), StreamParams);

作成したVlcPlayerをContentControlなどのContentに入れてあげればOK。
導入は簡単だと思います。

残念な点があるとすれば、操作が基本的にXamlのコードビハインドになること。
そこで、ViewModelからバインドできるようにアレンジします。

3.VlcControlをViewModelから使用する

VlcContorlを操作するため、UserControlを作成し依存プロパティを追加します。

Xamlはこんな感じ。余計なものは省略しています。

VLCPlayer.xaml
<UserControl>
  <Grid>
    <ContentControl Name="PlayerContainer" />
  </Grid>
<UserControl>

で、UserControlのコードビハインド。
この例では依存プロパティとして「StreamPath」プロパティを追加しています。
StreamPathにViewModelからRTMPのパスを指定することで、再生先を切り替え出来ます。

VLCPlayer.xaml.cs

    public partial class VLCPlayer : UserControl
    {
        // Extra parameters to pass to the viewer media. I found a 2 seconds buffer cache makes playing much more stable.
        private const string StreamParams = ":network-caching=2000";

        public VlcControl vlcPlayer;
        VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "libvlc", IntPtr.Size == 4 ? "win-x86" : "win-x64"));

        #region StreamPath
        public static readonly DependencyProperty StreamPathProperty =
        DependencyProperty.Register("StreamPath",
                                    typeof(string),
                                    typeof(VLCPlayer),
                                    new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnStreamPathChanged)));

        public string StreamPath
        {
            get { return (string)GetValue(StreamPathProperty); }
            set { SetValue(StreamPathProperty, value); }
        }

        private static void OnStreamPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            VLCPlayer ctrl = obj as VLCPlayer;
            if (ctrl == null) return;

            ctrl.SetCamera();
        }
        #endregion


        public VLCPlayer()
  {
   InitializeComponent();
  }

        public void SetCamera()
        {
            if (vlcPlayer != null)
            {
                vlcPlayer.Dispose();
                vlcPlayer = null;
            }

            if (string.IsNullOrEmpty(this.StreamPath)) return;

            vlcPlayer = AddPlayer(this.StreamPath, PlayerContainer);
        }


        private VlcControl AddPlayer(string streamPath, ContentControl container)
        {
            var player = new VlcControl();

            container.Content = player;

            player.SourceProvider.CreatePlayer(VlcLibDirectory);
            player.SourceProvider.MediaPlayer.Play(new Uri(streamPath), StreamParams);

            return player;
        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            try
            {
                if (vlcPlayer != null)
                {
                    vlcPlayer.Dispose();
                    vlcPlayer = null;
                }
            }
            catch (Exception ex) { }
        }
    }



2020年5月2日土曜日

WPFで依存プロパティでエラーが発生する。(xdg0012 )

WPFでユーザコントロールに依存プロパティ(DependencyProperty)を追加しました。
追加方法がいろんなサイトで紹介されているので割愛。

下のように「IsMute」プロパティを追加したのですが、エラーになってしまいます。


xdg0012 ’IsMute’は認識されないかアクセスできません

症状は以下の感じ。

  • デザイナが表示されない
  • ビルドすると動作する
  • リビルドしても解消しない
  • VisualStudio2019で発生
依存プロパティは慣れていないとミスすることも多いと思うけど、どう書き換えてもエラーが消えない・・・


いろいろ試しましたが、結果的にVisualStudioの問題だったみたい。
更新すると発生しなくなりました。


2020年4月22日水曜日

WPFのWebBrowserでHTML5を表示する

WPFと書いていますが、WPFに限らずWebBrowserコントロールでHTML5表示する方法です。
実際にはHTML5に対応するのではなく、プログラム中で使用されるIEのバージョンを変更する内容になります。(デフォルトではIE7が使用されます)

レジストリエディタを使用します。
よくわからない人は、止めておきましょう。

1.レジストリエディタの起動

スタートメニューを開いた状態で「regedit」と入力します。レジストリエディタが表示されるのでクリックして起動します。


2.レジストリの追加

レジストリエディタ内のエクスプローラをたどって、
HKEY_LOCAL_MACHINE-> SOFTWARE-> Wow6432Node-> Microsoft-> Internet Explorer-> MAIN-> FeatureControl-> FEATURE_BROWSER_EMULATION
まで移動します。


右側のキーの空のスペースを右クリックして、コンテキストメニューを表示し、
新規-> DWORD(32ビット)値を選択します。

 追加されたキーの名前を、作成したプログラム名(例ではMyApplication.exe)に変更します

 キーをダブルクリックして値を登録します。
表記を「10進数」に変更して「10001」を入力します


「10001」は「IE11 Edge」を指定していることになります。
その他の値はMicrosoftのオフィシャルページを参照してください。

2019年4月3日水曜日

GitHubからSnoopのモジュールをダウンロードする

WPF開発ではSnoopが無いと辛いんですが、いつのまにかサイトが移転して、GitHubでの公開に変わったんですね。
EXEの公開場所がわかんなくて「ソースビルドしなきゃだめなの?」と少し彷徨ったので覚書。
GitHubでのモジュール公開の仕方って、これが普通なのかな?

SnoopのGitHubはこちら

Releaseのリンクを開くと、各モジュールとソースが公開されています。


リンク先には各バージョンのソースとモジュール(EXE)が置いてあります。
ソースが不要な人はzipかmsiをダウンロードすればOKですね。


Windows10でSnoopを実行するには、ランタイムのインストールが必要になるかもしれません。
その場合に困った人は、「Windows10に.Net Framework 3.5をインストールする方法」も参考にしてください。

2018年4月8日日曜日

MoveFocusはCtrlキーと併用できない

WPFのコントロール間のフォーカス移動にMpoveFocus()を使っていたのですが、うまく動かないケースがあります。

このような画面で、以下のような画面を作成します。
タブAのボタンにはMoveFocusでフォーカス移動するようにしてあります。




private void Button_Click(object sender, RoutedEventArgs e)
{
 var ctl = Keyboard.FocusedElement as UIElement;
 if (ctl != null)
 {
  ctl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
 }
}

これを普通に実行しても、何もおきません。

でも、Ctrlキーを押しながらボタンをクリックすると・・・

タブBに切り替わった!!

どうやら、TabControlは、Ctrl+Tabでタブの切り替えが発生するようです。
もしかして、MoveFocusメソッドで指定しているFocusNavigationDirection.NextがTabキーとして認識されている・・・?

この現象、フォーカスの移動先がTextBoxなどの場合は発生しなかったりなのですが、
もし、MoveFocusを使用することで予期しない動作をする場合は、確認していたほうがいいかも?

処理がメインスレッドで実行されているか判定する(CheckAccess)

画面に関する処理などはメインスレッドで実施する必要があります。
処理によっては、メインスレッドで実施されているかどうか判定が必要となります。

メインスレッドかどうかの判定には
Application.Current.Dispatcher.CheckAccess()
を使用します。
CheckAccess()はメインスレッドの場合にTrueを返します。


using System.Windows;

private void Execute()
{
 // メインスレッドから実行
 bool checkAccessA = Application.Current.Dispatcher.CheckAccess();

 Debug.WriteLine("メインスレッド [" + checkAccessA + "]");

 // タスク内で実行
 Task.Run(() =>
 {
  bool checkAccessB = Application.Current.Dispatcher.CheckAccess();

  Debug.WriteLine("別スレッド [" + checkAccessB + "]");
 });
}


このプログラムの実行結果は

メインスレッド [True]
別スレッド [False]

のようになります。

WPFでLivetを使用しているなら、
DispatcherHelper.UIDispatcherを使用できるので、あまり出番はないかもしれませんが・・・

2018年2月3日土曜日

EnableCollectionSynchronization使ってたのにExceptionが出た

WPFでDataGridにリストをバインドする際に、ObservableCollectionを使用しますが、
非同期処理でリストを更新するとExceptionが発生します。

そこで、EnableCollectionSynchronizationを使用して、lock処理を行います。


// Lock用のobjectを作成
private object lockObject = new object();

public ObservableCollection<Myitem> Result { get; set; }

public コンストラクタ()
{
    // 非同期での変更を可能にする
    Result = new ObservableCollection<Myitem>();
    BindingOperations.EnableCollectionSynchronization(this.Result, lockObject);
}

// これは非同期処理
private void Test()
{
    Result.Clear();
    for (int i = 0; i < 100; i++)
    {
        var item = new MyItem();
        Result.Add(item);
    }
}


なんとなく、これで良いと思っていたんだけど、これだと稀にExceptionが出るみたい。

非同期で処理すると、
System.ArgumentOutOfRangeException
インデックスは一覧の 範囲内になければなりません
(Index must be within the bounds of the List)



LockはEnableCollectionSynchronizationメソッドを使うだけじゃなくて、
更新される箇所にも使う必要があるみたい。


// Lock用のobjectを作成
private object lockObject = new object();

public ObservableCollection<Myitem> Result { get; set; }

public コンストラクタ()
{
    // 非同期での変更を可能にする 
    Result = new ObservableCollection<Myitem>();

    BindingOperations.EnableCollectionSynchronization(this.Result, lockObject);
}

// これは非同期処理
private void Test()
{
    // Lockを取得
    lock(lockObject)
    {
        Result.Clear();
        for (int i = 0; i < 100; i++)
        {
            var item = new MyItem();
            Result.Add(item);
        }
    }

}


例ではメソッドを全て囲っていますが、ClearやAddのみを対象としたほうがレスポンス良いかなと思います。