none
Windows Vista、7 のコンボボックスの挙動について RRS feed

  • 質問

  • Windows Vista、7 にて、編集不可コンボボックスに対してある操作を行うと、
    選択項目に空の項目がないにも関わらず、値が空になってしまう現象が発生します。
    なお、Windows 8.1 や 10 では発生しませんでした。

    【再現手順】
    1. 値のある編集不可コンボボックスを表示する
    2. コンボボックスの▼をクリックする(この時マウスを離さないこと)
    3. マウスを押下を維持したまま、ESCキーを押下する
    4. コンボボックスが閉じられ、値がクリアされる

    ※ 一度でもコンボボックスをクリック→離す動作をしてしまうと発生しない模様。
    その場合は、再度プログラム側からコンボボックスに値をセットすれば再現する模様。

    ちなみに、Windows 標準の電卓アプリやダイヤラーでは、
    コンボボックスの値が空になることが想定されていないのか、
    空にするとアプリがクラッシュしてしまいます。
    https://twitter.com/hk1v/status/735106508606689284
    https://twitter.com/hk1v/status/735120056481251328

    コンボボックスだったらなんでも発生するというわけでなく、
    コモンコントロール6.0(XPビジュアルスタイル)が有効であり、
    OS標準?のコンボボックスを使ったときのみ発生するようです。

    【現象発生】
    ・C#というか.NETのWinFormで使えるComboBoxの場合
    ・C++にてMFCで使えるComboBoxの場合
    ・Win32APIのCreateWindowEx()にてComboBoxを作成した場合

    自分が作ったプログラムに対しては、
    編集不可コンボボックスの場合でも空になっていないか
    チェックするようにソースを修正して対応しました。

    こんな操作する人たぶんいないと思うので、ほとんどの場合問題ないんでしょうけど、
    ESCキーで値が消せてしまうのって、コンボボックスの仕様なんでしょうか。
    (Win7以前の動作が正しいのか、Win8以降の動作が正しいのか。。。)


    【試した環境】
    ・Windows 10 Pro ビルド10586(i7-6700K, IntelHD)
    ・Windows 10 Pro ビルド10586(i7-4790K, GeForce980Ti)
    ・Windows 8.1 (i3-6100T, IntelHD)
    ・Windows 7 Professional SP1(i7-2600K, IntelHD)
    ・Windows 7 Home Premium SP1(i7-870, GeForce750Ti)
    ・Windows Vista Home Premium SP2(C2D T7500, IntelGMA)

    2016年5月29日 15:09

回答

  • コンボボックスは、「現在選択している項目」と「以前選択していた項目」とは別々に管理されています。そして、ドロップダウンリストが開いている状態でESCキーを押すと「以前選択していた項目」に値が戻るのが仕様のようです。これはドロップダウンリストの選択項目はマウスが動くたびに変化しますが、ユーザーが選択を取りやめたいと思ったときにESCキーで以前の選択に戻せるようにするためだと思います。

    通常はドロップダウンリストが閉じているときはこの2つは同じ値になっているのが正常だと思いますが、Windows 7 ではプログラムから項目を変更(CB_SETCURSEL)した場合は、「現在選択している項目」は更新されますが、「以前選択していた項目」は更新されないようです。Windows 8や10ではAPIからの変更で「以前選択していた項目」も変更されました。

    コンボボックスを作成して項目を追加した段階では「現在選択している項目」と「以前選択していた項目」は-1で初期化されています。

    以上のことがご質問のような挙動として現れたのだと思います。

    ※「以前選択していた項目」を取得・変更するAPIはありません。
    ※ Windows 8、Windows 10においてもコンボボックスにフォーカスが当たっていてかつドロップダウンが開いていない状態でキーボード(「↑」「↓」、「PageUp/Down」、「Home/End」、アルファベットなど)で項目を変更した場合は、現在選択している項目」と「以前選択していた項目」は同期されないようです。
    • 編集済み kenjinoteMVP 2016年6月1日 4:40 内容を修正しました。
    • 回答としてマーク inovia 2016年6月1日 13:03
    2016年5月30日 1:57
  • 私はComboBoxを継承したコントロールを作り、そこでこんな対応をしています。

    protected override void OnKeyDown(KeyEventArgs e)
    {
    	if (this.DroppedDown)
    	{
    		if (e.KeyCode == Keys.Escape)
    		{
    			int index = this.SelectedIndex;
    			this.DroppedDown = false;
    			if (this.SelectedIndex != index &&
    				this.SelectedIndex == -1)
    			{
    				this.SelectedIndex = index;
    			}
    			e.Handled = true;
    		}
    	}
    	if (!e.Handled)
    	{
    		base.OnKeyDown(e);
    	}
    }

    • 回答としてマーク inovia 2016年6月1日 13:03
    2016年5月31日 2:19

すべての返信

  • Windows のコントロールの不具合でしょう。
    もっとも、Windows 7 やそれ以前の OS はサポートフェーズの状態からして、修正されることはありませんので、この現象を気にするのであれば、アプリ側で対策するしかありません。
    2016年5月29日 21:38
    モデレータ
  • コンボボックスは、「現在選択している項目」と「以前選択していた項目」とは別々に管理されています。そして、ドロップダウンリストが開いている状態でESCキーを押すと「以前選択していた項目」に値が戻るのが仕様のようです。これはドロップダウンリストの選択項目はマウスが動くたびに変化しますが、ユーザーが選択を取りやめたいと思ったときにESCキーで以前の選択に戻せるようにするためだと思います。

    通常はドロップダウンリストが閉じているときはこの2つは同じ値になっているのが正常だと思いますが、Windows 7 ではプログラムから項目を変更(CB_SETCURSEL)した場合は、「現在選択している項目」は更新されますが、「以前選択していた項目」は更新されないようです。Windows 8や10ではAPIからの変更で「以前選択していた項目」も変更されました。

    コンボボックスを作成して項目を追加した段階では「現在選択している項目」と「以前選択していた項目」は-1で初期化されています。

    以上のことがご質問のような挙動として現れたのだと思います。

    ※「以前選択していた項目」を取得・変更するAPIはありません。
    ※ Windows 8、Windows 10においてもコンボボックスにフォーカスが当たっていてかつドロップダウンが開いていない状態でキーボード(「↑」「↓」、「PageUp/Down」、「Home/End」、アルファベットなど)で項目を変更した場合は、現在選択している項目」と「以前選択していた項目」は同期されないようです。
    • 編集済み kenjinoteMVP 2016年6月1日 4:40 内容を修正しました。
    • 回答としてマーク inovia 2016年6月1日 13:03
    2016年5月30日 1:57
  • 但し書きで触れられているようですが、内部実装は非公開なので直接触ることには賛同しかねます。
    現実的に、選択範囲外(-1 など)として得られるのでそのときは前回値に復元する、あるいは次に進めなくするで済む話だと考えています。

    (デバッグでウォッチして得られた情報かもしれませんが、リバースエンジニアリングに相当するかもしれないと考えると、危うい話もあるので)

    2016年5月30日 14:08
    モデレータ
  • 私はComboBoxを継承したコントロールを作り、そこでこんな対応をしています。

    protected override void OnKeyDown(KeyEventArgs e)
    {
    	if (this.DroppedDown)
    	{
    		if (e.KeyCode == Keys.Escape)
    		{
    			int index = this.SelectedIndex;
    			this.DroppedDown = false;
    			if (this.SelectedIndex != index &&
    				this.SelectedIndex == -1)
    			{
    				this.SelectedIndex = index;
    			}
    			e.Handled = true;
    		}
    	}
    	if (!e.Handled)
    	{
    		base.OnKeyDown(e);
    	}
    }

    • 回答としてマーク inovia 2016年6月1日 13:03
    2016年5月31日 2:19
  • 派生でイベントを書き換えるのが対応としては正攻法かと思います。

    > Azulean さんへ

    前の私の投稿は、Win7とWin8以降との挙動の違いにつて調べ分かったことを1つの情報として提示したもので、賛同を得るという内容ではありませんでした。問題がありましたら削除します。

    2016年5月31日 2:44
  • 前の私の投稿は、Win7とWin8以降との挙動の違いにつて調べ分かったことを1つの情報として提示したもので、賛同を得るという内容ではありませんでした。問題がありましたら削除します。

    私自身は、問題かどうかを判断する立場にはありませんので、あくまで個人的な意見を寄せているだけと捉えてください。
    (モデレーターロールはいただいていますが、あくまで回答としてマークなどのごく限られた補助が目的です)

    さて、「調べていてわかったこと提示した」ということですが、全員が「読むだけ」で済ます保障はないので、「危ない情報は載せない」方が良いと個人的には思っています。今回の質問者だけではなく、第三の同じ困りごとを抱えた方も見る可能性もあり、広まっていくものであり、いつか利用されてしまう可能性があるためです。(もっとも、利用した人の自己責任であることは変わりません)

    また、Windows 製品にはリバースエンジニアリングを禁じる EULA があるので、それに抵触するとみなされると、調べた人がライセンス違反となるので今回の情報の提示や動き方が気になったという具合です。
    (コントロールが具体的にどのアドレスをどのような情報として扱っているかは、逆アセンブルか、メモリを見て推測・動かして裏付けをとることで中身を探ったかになりそうなので。もっとも、この行為をリバースエンジニアリングとみなされるのかどうか、私は知らないのですが…リスクを考えてやらないようにしています)

    2016年5月31日 13:46
    モデレータ
  • ご指摘ありがとうございます。

    私はリバースエンジニアリングの範囲についてよくわかっていないため
    今回の調べたことがそれに当たるかどうかわかりませんでした。
    (ただ悪意は無いということは言えますが・・・)
    契約に違反したりそれを助長するのは本意ではないので、
    可能性があればやめたいと思います。
    「危ない情報」についても同様ですね。
    「危ない情報」と把握していながら投稿することはないですが、
    結果的に「危ない情報」として広まってしまうのは本意ではないです。

    以前の投稿を修正したいと思います。
    2016年6月1日 2:29