single.php

C# WinUI 3アプリでコントロールのクリックとダブルクリックを区別する方法

C# WinUI 3アプリを作っていく途中で、躓いた部分を備忘録的に投稿します。今回はWinUI3プロジェクトでXaml上のButtonコントロールなどで追加したClickイベントでシングルクリックとダブルクリックを区別させる方法です。

XamlのコントロールにはClickのみ

C#のWinUI 3プロジェクトでメインウィンドウはXamlで構成しますが、配置するButtonコントロールなどにはダブルクリックなどのイベントを処理するプロパティが用意されていません。

クリックイベントは、つぎのようにプロパティを追加して、CSファイル側で処理を受け取ることができます。

<Button Click="ImageButton_Click" />

色々調べてみて、タイマー(System.Threading.Timer)を利用することでクリックとダブルクリックを区別して処理することができました。

Clickイベントで使って判定

元ネタはMicrosoft公式ページの[クリックとダブルクリックを区別する方法 (Windows フォーム .NET)]を参考にしています。

WinUI3は System.Windows.Forms 名前空間が使えないので別のアセンブリとWin32で代用しています。

まずは、Xaml側の修正。

クリックイベントが追加できるコントロール(Button コントロールなど)のタグを次のように修正します。

<Button Click="ImageButton_Click" ClickMode="Press" />

[ImageButton_Click]の部分はCSファイルで受け取るプロシージャー名を指定します。

また、ClickModeは、[Hover/Press/Release]の3種類が指定できますが、マウスボタンが押された直後にイベントが動作する[Press]を指定しておきます。

Timerでダブルクリックを判定

Xamlを編集できたら、CSファイルでクリックとダブルクリックを判定する処理を追加していきます。

具体的な流れとしては、タイマーを使います。

「最初のクリック」が発生した直後、「ダブルクリックと判定するまでの時間」まで動作しないタイマーを作動させます。

タイマーが動作していない間(下の図のオレンジ色の範囲内)に「2回目のクリック」が発生したら、ダブルクリックになります。

また、タイマーイベントが発生(下の図の緑色部分)で「2回目のクリック」が発生した場合はクリックという判定を行います。

「ダブルクリックと判定するまでの時間」はWindows側で任意に設定ができるため、C#の場合は[SystemInformation.DoubleClickTime]で取得ができますが、これもWinUI3では使えないのでWin32のGetDoubleClickTimeで代用します。

具体的には、次のコードで取得を行います。

[DllImport("user32.dll")]
static extern uint GetDoubleClickTime();

タイマーの作成と判定処理

Win32でダブルクリックと判定するまでの時間が取得できたら、実際の処理を追加していきます。

具体的には次の処理を追加します。

まずはMainWindowの直前に各種変数を追加します。

private Timer m_dblClickTimer;
 //タイマーイベント本体
private DateTime m_lastClick; //最後にクリックされた時間
private bool m_bDblClick; //判定用フラグ
private TimeSpan m_tsDblClickMaxTime; //ダブルクリックと判定する時間(保存用)

次にMainWindow関数でタイマーを作成します。

public MainWindow()
{
  this.InitializeComponent();

  //ダブルクリックと判定する時間取得
  m_tsDblClickMaxTime = TimeSpan.FromMilliseconds(GetDoubleClickTime());
  
  //ダブルクリック判定用タイマー
  m_dblClickTimer = new Timer(new TimerCallback(TimerProc));
  //念のためタイマー停止
  m_dblClickTimer.Change(Timeout.Infinite, Timeout.Infinite);

}

これで準備ができました。

m_tsDblClickMaxTime は後でTimeSpan型で差分を求めるために、初期化部分でWin32から取得してUINT型から変換して保存しています。

最初のクリック時の処理

Xamlで追加した[Click]プロパティのイベントハンドラーで「最初のクリック」処理を追加します。(下の図の色が付いている部分を作ります)

具体的には次の処理を追加します。

private void ImageButton_Click(object sender, RoutedEventArgs e)
{
  //クリック時間を保存
  m_lastClick = DateTime.Now;
  //クリックタイマー作動
  m_dblClickTimer.Change(m_tsDblClickMaxTime.Milliseconds, 0);
  m_bDblClick = true;
}

後でクリック時間の比較のために使う「最初のクリック」時間を変数に保存してから、ダブルクリックの判定時間(ミリ秒)後に発生するタイマーをスタートさせます。

2回目のクリック時の処理(ダブルクリック判定)

「最初のクリック」処理に「2回目のクリック時の処理」を追加します。(下の図の色が付いている部分を作ります)

具体的には次の処理を追加します。

private void ImageButton_Click(object sender, RoutedEventArgs e)
{

  if(m_bDblClick == true)
  {

    //2回目のクリック時間と最初のクリック時間を比較
    TimeSpan length = DateTime.Now - m_lastClick;
    if (length < m_tsDblClickMaxTime)
    {
      //既定の時間内であればダブルクリックと判定
      //タイマー停止とフラグを初期化
      m_dblClickTimer.Change(Timeout.Infinite, Timeout.Infinite);
      m_bDblClick = false;
      Debug.Print("ダブルクリック!");
      return;
    }
  }

  //クリック時間を保存
  m_lastClick = DateTime.Now;
  //クリックタイマー作動
  m_dblClickTimer.Change(m_tsDblClickMaxTime.Milliseconds, 0);
  m_bDblClick = true;
}

保存しておいたクリック時間と2回目のクリック時間と比較してダブルクリックの判定時間(ミリ秒)内であればダブルクリックとして処理します。

2回目のクリック時の処理(クリック判定)

タイマーイベントに「2回目のクリック時の処理」を追加します。(下の図の色が付いている部分を作ります)

初期化時にTimerCallbackとして宣言した関数にクリック時のイベントを追加します。

イベントは、「最初のクリック」からダブルクリックとして判断される時間が経過してから発生するので、この関数が起動した時点でクリックイベントという訳になります。

private void TimerProc(object state)
{
  //タイマー停止
  m_dblClickTimer.Change(Timeout.Infinite, Timeout.Infinite);
  Debug.Print("クリック!");
}

タイマーを停止してクリックとして処理します。

厳密に言うと、クリックが発生した場所を特定して同じ場所でクリックされた場合など細かな処理が必要ですが、この程度の処理でもダブルクリックのイベントとして代用ができます。

ダブルクリックとして判断する最大時間まで待つようになるので、通常のクリックイベントが少し遅れるようになるのは、あらかじめご容赦ください。

まとめ

今回は、WinUI 3 アプリのXamlで追加した[Button]コントロールなどの[Click]プロパティを使ってダブルクリックを判定する方法について書きました。

Windowsフォームアプリであれば、コントロールに「DblClick」などダブルクリック時のイベントを設定できますが、WinUI3の場合にはClickイベントのみしかないため、ダブルクリックを取得する方法がありません。

そのためダブルクリックを判定する処理を実装することで代用することが可能です。

C#のWinUI 3アプリでダブルクリックのイベントを取得したい場合の参考になれば幸いです。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です