7いいね以上 or 4ストックでWinFormを使った実装例(とGitリポジトリ)を追記します! → 面倒だったけど頑張って書いた。
9いいね以上 」 6ストックで出来る限りTSFについて分かった事書きます。)
10いいね以上で実測結果を入れます。(最終更新)
単に面倒なだけのタダ働きクソ記事
翻訳タイトル:C# Moving Focus Between Input Fields with the Num+ Key: IME Mode Detection
特定のキー(Addキー)入力におけるコントロールのフォーカス移動
※これは単なるSEO対策です。
前置き
気まぐれで見つけた質問に注視(まあ守備範囲だったんで)
尚、初回はグローバルフック使えとか回答してしまった。
特定のキーで入力項目間移動するプログラムを作成したが、繰り返し操作を行うとエラーでプログラムが落ちてしまう。
https://learn.microsoft.com/ja-jp/answers/questions/5478020/question-5478020
この質問では、imm32.dll
を使用してImmGetVirtualKey
を利用してIMEを検知するというアプローチをしているが
繰り返し操作をする内に、入力項目間移動が行えず全角「+」が入力されてしまう項目が現れ、
さらに操作を繰り返していると以下のエラーが発生しプログラムが落ちてしまいました。
と報告している。
恐らくこれも別のアプローチで解決できると思われるが情報の安売りはしないので他を当たるように。 というか比較的簡単に判明したんだが。
→5いいね以下でimm32.dllを使った項目は削除
→案件に対応するために書かざる負えなくなった。
以下はなんかしょーもないなと思いながら実装。つか別の手段探そうとかないわけ。
尚、特定のキーとは+キーのことで、恐らく顧客の手癖か何かなのだろう。真っ先に思いつくのは矢印キー辺りだし不可解だが。
目次
-
Winform版の特定のキーによるフォーカス移動とIME判定(2025/8/16)
→ ※更新日より1ヵ月後までに 8いいね以上 or 5ストック以上なければこのカテゴリは消します。(限定公開にする)
参考&関連リンク
C#のWPFでフォーカスを移動する
WPFが対象
次のタブオーダーのコントロールをアクティブに(選択、フォーカスを移動)する
dobonなのでWinform向け。
カーソルキー(方向キー)を用いてフォームのコントロールのフォーカスを移動させる
実装が冗長すぎる。
【WPF】フォーカス遷移について知っておきたい3つのキーワード
GitHub(WPF/Winform)
.net 9.0
VisualStudio 2022
更新日から1ヵ月後までに"維持費"としてStarを要求します。 Starを付けない場合は要らないと判断してリポジトリを非公開化します。相変わらずがめつい。
ProJect内にはそれぞれ WPF版、TSF実装、Winform版が入っています。
WPF版
実行結果
以下のCodeを忠実に実装すれば以下の結果を得られる。
※モバイル端末で表示されない場合はリロードしてくだちい。
案件用のXAML定義(やたらと多いのでAIに作らせた)
....簡易的に数個で良かったと思うが。
<Window x:Class="PlusButtonToShiftIndexer.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PlusButtonToShiftIndexer"
mc:Ignorable="d"
Loaded="Window_Loaded"
KeyDown="Window_KeyDown"
PreviewKeyDown="Window_PreviewKeyDown"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!-- 左列 -->
<StackPanel Grid.Column="0">
<TextBlock Text="初期値-タブ有無" Margin="0,0,0,5"/>
<RadioButton Content="○あり" Margin="0,0,0,5"/>
<RadioButton Content="×なし" Margin="0,0,0,5"/>
<TextBlock Text="ラベル幅" Margin="0,0,0,5"/>
<TextBox
x:Name="inputer"
PreviewKeyDown="TextBox_PreviewKeyDown"
TextChanged="TextBox_TextChanged"
TextInput="inputer_TextInput"
PreviewTextInput="inputer_PreviewTextInput"
Text="文字" Width="100"/>
</StackPanel>
<!-- 中央列 -->
<StackPanel Grid.Column="1">
<TextBlock Text="制御コード設定" Margin="0,0,0,5"/>
<CheckBox Content="○初期値" Margin="0,0,0,5"/>
<CheckBox Content="入力項目幅" Margin="0,0,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="活性" Margin="0,0,5,0"/>
<ComboBox Width="100">
<ComboBoxItem>あり</ComboBoxItem>
<ComboBoxItem>なし</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="必須性" Margin="0,0,5,0"/>
<ComboBox Width="100">
<ComboBoxItem>あり</ComboBoxItem>
<ComboBoxItem>なし</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<!-- 右列 -->
<StackPanel Grid.Column="2">
<TextBlock Text="入力データ" Margin="0,0,0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="文字" Margin="0,0,5,0"/>
<TextBox Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="数値" Margin="0,0,5,0"/>
<TextBox Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="整数" Margin="0,0,5,0"/>
<TextBox Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="数値 (DECIMAL)" Margin="0,0,5,0"/>
<TextBox Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="日付データ" Margin="0,0,5,0"/>
<TextBox Width="100"/>
</StackPanel>
</StackPanel>
</Grid>
<!-- フッター部分 -->
<StackPanel Grid.Row="1" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Enter" Width="100" Margin="0,0,10,0"/>
<Button Content="F1: 取消" Width="100"/>
<Button Content="F11: 確定" Width="100"/>
<Button Content="Shift+F9: 終了" Width="100"/>
</StackPanel>
</Grid>
</Window>
スクショは案件に配慮して貼らないで置く。
実行画面貼るクセがあるんで意味なかった件(笑)
例のごとくe.Handledを使う
ふつーは真っ先に思いつくと思うが該当のイベントが見つからなかったのだろう。
TextBoxのイベントはPreviewTextInput
イベントで入力直前のイベントをキャンセル出来ると分かった。
似たような物だと思うが最終的にPreviewKeyDownを採用。
Winformはコメントで要望あったらやる(但し5いいね以上)
参考:UIElement.PreviewTextInput イベント
イベント関係はいちいち把握しないといけないから面倒ですね。
なお、この案件ではcheckBoxでも同じくキャンセル実装が必要(だがめんどいから省く)
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
//nugetから InputSimulatorをインストール
InputSimulator simulator = new InputSimulator();
if (e.Key == Key.Add)
{
simulator.Keyboard.KeyPress(VirtualKeyCode.TAB); // Tabキーを送信
e.Handled = true; // イベントを処理済みにする
}
}
IMEの判定を行う(WPF・Winform兼用)
1ヵ月以内に5いいね以上付かない場合この項目は削除されます!
以下のように、IMEが有効時に+キー
が入力されてしまう問題への対応。結局書く羽目になった。不運
ime Modeが有効どうかを判定
注意点:ImmGetContext
を使用する場合はリソースの解放が必要
[DllImport("imm32.dll")]
private static extern IntPtr ImmGetContext(IntPtr hWnd);
//指定されたウィンドウに関連付けられている入力コンテキストを取得します。
[DllImport("imm32.dll")]
private static extern bool ImmGetOpenStatus(IntPtr hIMC);
///IME が開いているかどうかを調べます。
[DllImport("imm32.dll")]
private static extern uint ImmGetVirtualKey(IntPtr hWnd);
//IMEが有効になる前の元々のキーコードを取得できる関数
[DllImport("imm32.dll")]
private static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
//ImmGetContextで使用したhWnd、hIMCを解放(解放しないと不安定になる)
private const int NI_CLOSECANDIDATE = 0x0011;
private const int CPS_CANCEL = 0x0004;
private const int NI_COMPOSITIONSTR = 0x0015;
private bool IsImeOn()
{
//WindowInteropHelper:このクラスのメンバーを使用すると、呼び出し元は
//Win32 HWND と WPF Windowの親 HWND に内部アクセスできます。
WindowInteropHelper helper = new WindowInteropHelper(this);
IntPtr hWnd = helper.Handle;
IntPtr hIMC = ImmGetContext(hWnd);
if (hIMC != IntPtr.Zero)
{
bool isImeOpen = ImmGetOpenStatus(hIMC);
return isImeOpen;
}
return false;
}
参考
事前にフォーカスしておく
private IntPtr winHandle; // ウィンドウハンドルをIntPtrで宣言
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var main = sender as Window;
inputer.Focus();
winHandle = new WindowInteropHelper(this).Handle;
}
TextBoxのPreviewKeyDownイベント
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
IntPtr hWnd = winHandle;
IntPtr hIMC = ImmGetContext(hWnd);
if (hIMC != IntPtr.Zero)
{
uint virtualKey = ImmGetVirtualKey(hWnd);
if (virtualKey != 0) // IMEが処理したキーがある場合
{
ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0); // IMEバッファーをキャンセル
if (IsImeOn()) //IMEがOnのとき
e.Handled = true; // 入力イベントをキャンセル
//すぐにウィンドウハンドルを開放する
ImmReleaseContext(hWnd, hIMC);
}
}
}
これでIMEがオンの時も+が入力されなくなることが証明された
私はなんでどうでもいい他人の案件やってるんだろう
参考
キー送信
WPFで.net9.0だとSendKey使えないので、nugetからInputSimulatorをインストールする
ImmGetContext を使用したら必ずImmReleaseContext
で解放すること
参考:https://katahiromz.web.fc2.com/colony3rd/imehackerz/ja/ImmReleaseContext.html
MainForm内のTextBoxを列挙する
今更触りたくないし。ただ間違いなく似たようなアプローチで解決する
こうすればコンストラクタでMainForm内の全てのTextBoxに対し自動的にPreviewKeyDown
イベントを割り当てることが出来る。とても便利。
Codeは以下の記事を参考にしている。
using System.Windows;
namespace PlusButtonToShiftIndexer
{
static class DependencyObjectExtension
{
/// <summary>
/// WalkInChildrenメソッドの本体
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <param name="act">Action</param>
private static void Walk(DependencyObject obj, System.Action<DependencyObject> act)
{
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (child is DependencyObject)
{
act(child as DependencyObject);
Walk(child as DependencyObject, act);
}
}
}
/// <summary>
/// 子オブジェクトに対してデリゲートを実行する
/// </summary>
/// <param name="obj">this : DependencyObject</param>
/// <param name="act">デリゲート : Action</param>
public static void WalkInChildren(this DependencyObject obj, Action<DependencyObject> act)
{
if (act == null)
throw new ArgumentNullException(obj.Dispatcher.ToString());
Walk(obj, act);
}
}
}
public MainWindow()
{
InitializeComponent();
DependencyObjectExtension.WalkInChildren(this, (child) =>
{
if (child is TextBox textBox)
{
//全てのTextBoxにイベントを登録
textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
}
});
}
TSF(Text Services Framework)を使ったフォーカス遷移 & IME判定
Move Focus Using the Text Services Framework (TSF)
※これ使った実装例が殆ど存在しないからいいね6以上を要求します。(投稿日から1か月後に未達だったらここだけ消します)
実行結果
実装例
TsfImeHelper.cs
IMEの判定は簡易的でTSF入力コンテキストが存在する=IMEが有効である」
でIME Modeを間接的に検知している。
これで問題ないと思うがWin32APIの実装の方が確実。しかし、TSF実装の方がオーバーヘッドが少なくて軽いという利点がある。
※非static化し、IDsposableを継承させた。理由は下記参照。
using System.Runtime.InteropServices;
namespace PlusButtonToShiftIndexer.TSF
{
public class TsfImeHelper : IDisposable
{
[ComImport, Guid("AA80E7F0-2021-11D2-93E0-0060B067B86E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface ITfThreadMgr
{
void Activate(out int clientId);
void Deactivate();
void CreateDocumentMgr(out ITfDocumentMgr docMgr);
void EnumDocumentMgrs(out object enumDocMgrs);
int GetFocus(out ITfDocumentMgr docMgr);
void SetFocus(ITfDocumentMgr docMgr);
void AssociateFocus(IntPtr hwnd, ITfDocumentMgr newDocMgr, out ITfDocumentMgr prevDocMgr);
void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus);
}
private interface ITfDocumentMgr { /* 今回は空でOK */ }
[DllImport("msctf.dll")]
private static extern int TF_CreateThreadMgr(out ITfThreadMgr pptim);
private ITfThreadMgr? _threadMgr;
private int _tsfHResult;
private bool _disposed;
public TsfImeHelper()
{
+ if (_threadMgr != null) return;
// 2重呼び出しをさけるため、コンストラクタから呼び出す
// 2重呼び出しだとハングする
+ _tsfHResult = TF_CreateThreadMgr(out _threadMgr);
// HRESULT 成功判定
if (_tsfHResult != 0 || _threadMgr == null)
{
// 初期化失敗時は解放
_threadMgr = null;
}
}
public bool IsImeActive()
{
if (_threadMgr == null || _tsfHResult != 0)
return false;
ITfDocumentMgr? docMgr = null;
try
{
int hr = _threadMgr.GetFocus(out docMgr);
if (hr != 0 || docMgr == null)
return false;
// ここでIMEの詳細チェックを入れる場合は docMgr のContextを確認
return true;
}
catch
{
return false;
}
finally
{
if (docMgr != null)
Marshal.ReleaseComObject(docMgr);
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
if (_threadMgr != null && Marshal.IsComObject(_threadMgr))
{
+ Marshal.ReleaseComObject(_threadMgr);
_threadMgr = null;
}
}
}
}
参考:Marshal.ReleaseComObject(Object) メソッド
外観
何の変哲もないUIだが、書かないのも不親切だろう。
<Window x:Class="PlusButtonToShiftIndexer.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PlusButtonToShiftIndexer"
mc:Ignorable="d"
PreviewKeyDown="TextBox_PreviewKeyDown"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl>
<TabItem Header="Test2">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- TextBox -->
<TextBox x:Name="Test2_TextBox1" Grid.Row="0" Grid.Column="0" Margin="5" Text="TextBox 1"/>
<TextBox x:Name="Test2_TextBox2" Grid.Row="0" Grid.Column="1" Margin="5" Text="TextBox 2"/>
<TextBox x:Name="Test2_TextBox3" Grid.Row="1" Grid.Column="0" Margin="5" Text="TextBox 3"/>
<!-- Button -->
<Button x:Name="Test2_Button1" Grid.Row="1" Grid.Column="1" Margin="5" Content="Button 1"
Click="Test2_Button1_Click"
/>
<Button x:Name="Test2_Button2" Grid.Row="2" Grid.Column="0" Margin="5" Content="Button 2"/>
<Button x:Name="Test2_Button3" Grid.Row="2" Grid.Column="1" Margin="5" Content="Button 3"/>
<!-- CheckBox -->
<CheckBox x:Name="Test2_CheckBox1" Grid.Row="3" Grid.Column="0" Margin="5" Content="CheckBox 1"/>
<CheckBox x:Name="Test2_CheckBox2" Grid.Row="3" Grid.Column="1" Margin="5" Content="CheckBox 2"/>
<CheckBox x:Name="Test2_CheckBox3" Grid.Row="4" Grid.Column="0" Margin="5" Content="CheckBox 3"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindowの実装
IMEバッファーのクリアは割愛したため、大分シンプル。
using PlusButtonToShiftIndexer.TSF;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace PlusButtonToShiftIndexer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DependencyObjectExtension.WalkInChildren(this, (child) =>
{
if (child is TextBox textBox)
{
textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
if (child is UIElement uiElement)
{
TextCompositionManager.AddPreviewTextInputHandler(uiElement, OnTextInput);
}
}
});
}
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
var focused = Keyboard.FocusedElement;
//IsImeActive() 自体でも threadMgr を作っている
using (var _tsf = new TsfImeHelper())
{
// IMEオン時は無効
if (e.Key == Key.Add && !_tsf.IsImeActive())
{
e.Handled = true; // 通常の入力をキャンセル
var request = new TraversalRequest(FocusNavigationDirection.Next);
var element = (FrameworkElement)sender;
element.MoveFocus(request);
// UIElementの場合
if (focused is UIElement uie)
{
bool moved = uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
if (!moved)
{
request = new TraversalRequest(FocusNavigationDirection.First);
uie.MoveFocus(request);
}
}
}
}
}
}
IME BufferのクリアはWin32APIを使った方法以外には存在しない。
→ IMEBufferは読み取り専用(ReadOnly)。
→ 変換候補を閉じることで間接的に実現できる
したがって前回と同じなので割愛する。
TF_CreateThreadMgr関数 (msctf.h)
について
https://learn.microsoft.com/ja-jp/windows/win32/api/msctf/nf-msctf-tf_createthreadmgr
に
TF_CreateThreadMgr関数は、COM を初期化せずにスレッド マネージャー オブジェクトを作成します。 呼び出し元プロセスは、Msctf.dll が所有するオブジェクトに対して適切な参照カウントを維持する必要があるため、このメソッドの使用はお勧めしません。
とあるので、適切に破棄する必要がある。
どういうときに破棄すべきか
長時間動くアプリ(エディタ、チャットアプリなど)では、解放しないとプロセス内でメモリやハンドルを消費し続ける。
最悪の場合ハングするので、例外処理やIDisposable
が必要である
尚、その前にアプリが終了されればリソースは適切に開放される。
ITfThreadMgr インターフェース(COM Interface)のGPUIDについて
生成AIが出した属性とGPUIDなんでリファレンスが必要だったんだけど、いくらググっても出てこないネタ元が謎だった。
//この部分
[ComImport, Guid("AA80E7F0-2021-11D2-93E0-0060B067B86E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface ITfThreadMgr
{
どうにか見つけ出したのはコレ
View the source code for any class in the .NET framework. (dotnetのソースコードを探せる)
今後のソース調査の参考にしたい。(なんか以前も誰かが提示してたけど記事消したんで)
なんとも面倒なことにInterFace名ごとに一意のGPUIDが指定されているから、いちいち調べないといけないというわけ。
どうりでWeb上に実装例が殆どないはずである。
使わせる気がないのかね....
Winform版の特定のキーによるフォーカス移動とIME判定(2025/8/16)
例によってTextBoxを対象にした際に入力されないようにしている。
※更新日より1ヵ月後までに 8いいね以上 or 5ストック以上なければこのカテゴリは消します。(限定公開にする)
Win32APIを使った実装はWPF版と同じ。
(強調表示が追加箇所)
注:TSFはWPF専用なので使えない。
TextChangedではイベントのキャンセルが出来ない。
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace NumPlusFocusNavigator_Winform
{
public partial class Mainform : Form
{
[DllImport("imm32.dll")]
private static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll")]
private static extern bool ImmGetOpenStatus(IntPtr hIMC);
[DllImport("imm32.dll")]
private static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
private static extern uint ImmGetVirtualKey(IntPtr hWnd);
//IMEが有効になる前の元々のキーコードを取得できる関数
[DllImport("imm32.dll")]
private static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue);
private const int NI_COMPOSITIONSTR = 0x0015;
private const int CPS_CANCEL = 0x0004;
public Mainform()
{
InitializeComponent();
this.WalkInChildren(ctrl =>
{
if (ctrl is MyTextBox tbox)
{
tbox.KeyPress += SendTabKey_KeyPress;
tbox.KeyDown += tbox_KeyDown;
tbox.KeyPress += textBox_KeyPress;
}
});
}
private void tbox_KeyDown(object? sender, KeyEventArgs e)
{
if (IsImeOn(Handle)) //IMEがOnのとき
e.Handled = true; // 入力イベントをキャンセル
IntPtr hWnd = this.Handle;
IntPtr hIMC = ImmGetContext(hWnd);
+ if (hIMC != IntPtr.Zero)
{
uint virtualKey = ImmGetVirtualKey(hWnd);
if (virtualKey != 0) // IMEが処理したキーがある場合
{
ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0); // IMEバッファーをキャンセル
//すぐにウィンドウハンドルを開放する
ImmReleaseContext(hWnd, hIMC);
}
}
}
+ private void SendTabKey_KeyPress(object? sender, KeyPressEventArgs e)
{
if (e.KeyChar == '+')
{
SendKeys.Send("{TAB}");
e.Handled = true; // 入力を無効化したい場合
}
}
private void Mainform_Load(object sender, EventArgs e)
{
myTextBox1.Focus();
}
private bool IsImeOn(IntPtr hWnd)
{
IntPtr hIMC = ImmGetContext(hWnd);
if (hIMC != IntPtr.Zero)
{
bool isImeOpen = ImmGetOpenStatus(hIMC);
ImmReleaseContext(hWnd, hIMC);
return isImeOpen;
}
return false;
}
private void textBox_KeyPress(object? sender, KeyPressEventArgs? e)
{
if (e.KeyChar == '+')
e.Handled = true;
else if (IsImeOn(Handle))
e.Handled = true;
}
}
Form内のTextBoxを列挙(取得)
namespace NumPlusFocusNavigator_Winform
{
internal static class EnumerateForm
{
/// <summary>
/// 内部的に再帰で子コントロールを探索するメソッド
/// </summary>
private static void Walk(Control parent, Action<Control> act)
{
foreach (Control child in parent.Controls)
{
if (child == null) continue;
if (act == null) return;
act(child);
Walk(child, act); // 再帰
}
}
/// <summary>
/// 子コントロールに対してデリゲートを実行する拡張メソッド
/// </summary>
public static void WalkInChildren(this Control parent, Action<Control> act)
{
if (parent == null) throw new ArgumentNullException(nameof(parent));
if (act == null) throw new ArgumentNullException(nameof(act));
Walk(parent, act);
}
}
}
実行結果
WndProcをOverRideするやり方も試したんだけど、TextBoxのIME入力と衝突したらしくハングして異常終了。沼った。
初心に却って単純な実装にした。
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_IME_COMPOSITION)
{
IntPtr hIMC = ImmGetContext(this.Handle);
if (hIMC != IntPtr.Zero)
{
// 未確定文字列の長さを取得
int size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, null, 0);
if (size > 0)
{
byte[] buffer = new byte[size];
ImmGetCompositionStringW(hIMC, GCS_COMPSTR, buffer, size);
string comp = Encoding.Unicode.GetString(buffer);
Console.WriteLine("未確定文字列: " + comp);
}
ImmReleaseContext(this.Handle, hIMC);
}
}
base.WndProc(ref m);
}
あとがき
まあ手札が増えた気もするんで良かったです(自分の完璧主義を呪っています)
それにしても十分対応できる案件だと思うが。
まず単純な実装を試そう。
→ IMEがONのとき+が入力されないようにする
のは案外面倒でした。調べはついたけど
今回は省いたがIMEモードの判定も出来ると分かったのが収穫だった。
記事の作成と検証にも手間がかかるんだから仕事回すか、いいねするかお金ください。。。 ....そのうちQiitaの支援機能使おう。
....質問に答えるだけで大分長い記事になってしまいました。
とにかくいいねください、いいねしないなら遠慮なく消します。 →流石にもう消さないとおもう(かなり多分)