Thinking Skeever

Skyrim/The Witcher 3 Modについてのあれこれ。FoModの作り方、Mod導入時のトラブル事例などのニッチな話を書いていきます。a.k.a. BowmoreLover@nexusmods

【.NET】【C#】【WinForm 】マウスでフォーカスされたテキストボックスのテキストを全選択する

マウスでテキストボックスをフォーカスしたときにテキストを全選択する方法についてまとめてみます。
グーグル検索すればStackoverflow等の記事がいくつか見つかりますが、特定条件での挙動が不完全だったので、その辺りの改善も含めて整理しました。

f:id:thinkingskeever:20151114030550p:plain


f:id:thinkingskeever:20151114030550p:plain

うまくいかない例

パッと思いつく方法としてGotFocusやEnterイベントハンドラでSelectAll()する方法があります。
この方法ではマウスでテキストボックスをフォーカスした場合にうまく全選択されません。
GotFocus/Enterイベント後に発生するマウスイベントで選択状態が解除されてしまうからです。

フォーム配置:
f:id:thinkingskeever:20170713195316p:plain

コード:

private void Form1_Load(object sender, EventArgs e)
{
	textBox1.Enter += TextBox1_Enter;
	textBox1.GotFocus += TextBox1_GotFocus;
}

private void TextBox1_Enter(object sender, EventArgs e)
{
	textBox1.SelectAll();
}

private void TextBox1_GotFocus(object sender, EventArgs e)
{
	textBox1.SelectAll();
}


別の方法として、GotFocusイベント発生後少し待ってからSelectAll()する方法もあります。
参考リンク:https://stackoverflow.com/questions/19095376/net-compactframework-textbox-selectall-on-gotfocus
テキストフォーカス後すぐに範囲選択操作をした場合、範囲選択が解除されてしまうので今一つです。

コード:

private void Form1_Load(object sender, EventArgs e)
{
	textBox1.GotFocus += TextBox1_GotFocus;
}

private void TextBox1_GotFocus(object sender, EventArgs e)
{
	Timer timer = new Timer { Interval = 100, Enabled = true };
	timer.Tick += (EventHandler)delegate
	{
		textBox1.SelectAll();
		timer.Dispose();
	};
}


f:id:thinkingskeever:20151114030550p:plain

解決策1

ググって調べた感じでは一番良さそうな方法です。
参考リンク:https://stackoverflow.com/questions/97459/making-a-winforms-textbox-behave-like-your-browsers-address-bar

タブキー/マウスクリックどちらでフォーカスしても全選択されますし、範囲選択操作が邪魔されることもありません。
f:id:thinkingskeever:20170713195319p:plain

ただし、非アクティブなフォームのテキストをクリックした場合にテキストが全選択されない欠点があります(解決策2参照)。


フォーム配置:
f:id:thinkingskeever:20170713195316p:plain

コード(参考リンクのコードのコメントを日本語化+デバッグメッセージ追加):

bool alreadyFocused;

private void Form1_Load(object sender, EventArgs e)
{
	textBox1.GotFocus += TextBox1_GotFocus;
	textBox1.MouseUp += TextBox1_MouseUp;
	textBox1.Leave += TextBox1_Leave;
}

private void TextBox1_GotFocus(object sender, EventArgs e)
{
	Debug.WriteLine("GotFocus");
	// マウスが押されていないときにだけテキストを全選択する。
	// こうすることによって、タブによるテキストボックスのフォーカスだけを処理する。
	if (MouseButtons == MouseButtons.None)
	{
		Debug.WriteLine("SelectAll on GotFocus");
		textBox1.SelectAll();
		alreadyFocused = true;
	}
}

private void TextBox1_MouseUp(object sender, MouseEventArgs e)
{
	Debug.WriteLine("MouseUp");
	// GoogleクロームのようなWebブラウザではMouseUpのときにテキストを選択している。
	// そこでは、テキストボックスがまだフォーカスされていない場合と
	// ユーザーがテキストを全く選択していない場合にのみ行っている。
	if (!alreadyFocused && this.textBox1.SelectionLength == 0)
	{
		Debug.WriteLine("SelectAll on MouseUp");
		alreadyFocused = true;
		textBox1.SelectAll();
	}
}

private void TextBox1_Leave(object sender, EventArgs e)
{
	Debug.WriteLine("Leave");
	alreadyFocused = false;
}

デバッグメッセージ出力:

タブキーでフォーカスされたとき:
  GotFocus
  SelectAll on GotFocus … マウスが押されていないのでSelectAll()する

マウスクリックでフォーカスされたとき:
  GotFocus … マウスが押されているのでSelectAll()しない
  MouseUp
  SelectAll on MouseUp … SelectAllされていない&テキスト選択されていないのでSelectAll()する

マウスクリックでフォーカスし、テキストを選択した場合:
  GotFocus … マウスが押されているのでSelectAll()しない
  (テキスト選択中)
  MouseUp … テキストが選択されているのでSelectAll()しない

フォーカスを失ったとき:
  Leave … alreadyFocusedをfalseにする


f:id:thinkingskeever:20151114030550p:plain

解決策2:解決策1の改善版

非アクティブなフォームのテキストをクリックした場合にテキストが全選択されない問題を対策してみます。

原因の調査

このときのデバッグメッセージ出力は次のとおりです:

非アクティブなフォームのテキストをクリックしたとき:
  GotFocus
  SelectAll on GotFocus
  MouseUp

フォームを非アクティブにしたとき:
  (なし)

GotFocusイベントハンドラ内の判定文 (MouseButtons == MouseButtons.None) が真となってSelectAll()が実行され、後続のMouseUpイベントで選択状態が解除されています。
Control.MouseButtonsではUser32.dllのGetKeyStateを呼び出してマウスボタンの状態を取得しています。GetKeyStateの説明には「入力メッセージが生成されたときのキーの状態を取得します」と書かれています。マウス操作でテキストボックスをクリックした延長で発生したイベントですから値が入っていても良さそうですが、とにかく値はMouseButtons.Noneとなってしまいます。

また、フォームを非アクティブにしたときにLeaveイベントが発生しないため、alreadyFocusedが適切にリセットされません。

対策

User32.dllのGetAsyncKeyStateを使って「その瞬間の」マウスボタンの状態を取得します。
Leaveイベントの代わりにLostFocusイベントハンドラでalreadyFocusedをリセットします。

コード:

bool alreadyFocused;

[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
private static extern short GetAsyncKeyState(int vkey);

private static MouseButtons AsyncMouseButtons
{
	get
	{
		MouseButtons buttons = (MouseButtons)0;
		if (GetAsyncKeyState((int)Keys.LButton) < 0) buttons |= MouseButtons.Left;
		if (GetAsyncKeyState((int)Keys.RButton) < 0) buttons |= MouseButtons.Right;
		if (GetAsyncKeyState((int)Keys.MButton) < 0) buttons |= MouseButtons.Middle;
		if (GetAsyncKeyState((int)Keys.XButton1) < 0) buttons |= MouseButtons.XButton1;
		if (GetAsyncKeyState((int)Keys.XButton2) < 0) buttons |= MouseButtons.XButton2;
		return buttons;
	}
}

private void Form1_Load(object sender, EventArgs e)
{
	textBox1.GotFocus += TextBox1_GotFocus;
	textBox1.MouseUp += TextBox1_MouseUp;
	textBox1.LostFocus += TextBox1_LostFocus;
}

private void TextBox1_GotFocus(object sender, EventArgs e)
{
	Debug.WriteLine("GotFocus");
	// マウスが押されていないときにだけテキストを全選択する。
	// こうすることによって、タブによるテキストボックスのフォーカスだけを処理する。
	if (AsyncMouseButtons == MouseButtons.None)
	{
		Debug.WriteLine("SelectAll on GotFocus");
		textBox1.SelectAll();
		alreadyFocused = true;
	}
}

private void TextBox1_MouseUp(object sender, MouseEventArgs e)
{
	Debug.WriteLine("MouseUp");
	// GoogleクロームのようなWebブラウザではMouseUpのときにテキストを選択している。
	// そこでは、テキストボックスがまだフォーカスされていない場合と
	// ユーザーがテキストを全く選択していない場合にのみ行っている。
	if (!alreadyFocused && this.textBox1.SelectionLength == 0)
	{
		Debug.WriteLine("SelectAll on MouseUp");
		alreadyFocused = true;
		textBox1.SelectAll();
	}
}

private void TextBox1_LostFocus(object sender, EventArgs e)
{
	Debug.WriteLine("LostFocus");
	alreadyFocused = false;
}

以上


----
記事の履歴
2017/07/13 : 記事作成

Copyright (C) 2015,2017 ThinkingSkeever, All Rights Reserved.
ブログの記事内に記載されているメーカー名、製品名称等は、日本及びその他の国における各企業の商標または登録商標です。
リンクはご自由に。記事の転載はご遠慮ください。記事を引用する場合はトラックバックするか元のURLを明記してください。