トップ回答者
MenuStrip.MenuDeactive が呼び出されない場合がある

質問
-
MenuStrip.MenuActivateは確実に呼ばれるがMenuStrip.MenuDeactiveが呼び出されない操作シーケンスが存在します。
この場合、MenuStripからユーザーのマウスカーソルが離れたことを検出できないためアプリケーションに適切な挙動をさせることができなくなります。
System.Windows.Formsを使用。
全画面を覆う枠なしのFormを表示し、画面上端にマウスカーソルを持ってくる(MouseEnter)と
予め登録しておいたMenuStripをVisibleにすることでメニューを表示し、
MenuStripからマウスカーソルが離れた段階(MouseLeave)で
MenuStripを再び消去するような動作をさせています。
実際にはプルダウンメニューを出したときにMenuActive/MenuDeactiveも呼び出されるため
これら4つのイベントを総合して表示状態を決めていますが、以下の操作をすると
メニューバーからマウスカーソルが離れてもメニューバーが表示されたままになります。
- マウスカーソルでメニューバーをクリックし、サブメニューのプルダウンを表示させる
- プルダウンにマウスカーソルを動かし、サブメニューの表示状態を確実にする
- 他のトップレベルメニューにマウスカーソルを動かし、
プルダウンメニューが消えた状態にする(この時クリックはしない) - マウスカーソルをメニューバーから離す。MenuStrip.MouseLeaveは呼び出されるが
MenuStrip.MenuDeactiveは呼び出されないためメニューバーが表示されたままになる
動作を改善できるいい方法は何かございませんでしょうか。
回答
-
言語が何かわからないのでC#で書いてみた
/// <summary>自動的に非表示になるMenuStrip</summary> public class AutoHideMenuStrip : MenuStrip { public AutoHideMenuStrip() : base() { timer = new Timer(); timer.Interval = 500; timer.Tick += new EventHandler(timer_Tick); } /// <summary>自動非表示するか</summary> public bool AutoHide { get { return _AutoHide; } set { _AutoHide = value; StopTimer(); } } private bool _AutoHide = false; /// <summary>自動非表示の監視時間</summary> public int AutoHideTime { get { return timer.Interval; } set { timer.Interval = value; } } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); if (this.Visible && !this.DesignMode) { StartTimer(); } } protected override void OnMenuDeactivate(EventArgs e) { base.OnMenuDeactivate(e); StopTimer(); } private System.Windows.Forms.Timer timer; private bool leaveCheck = false; private void StartTimer() { if (!this.DesignMode) { leaveCheck = false; timer.Start(); } } private void StopTimer() { if (AutoHide) { System.ComponentModel.CancelEventArgs e = new System.ComponentModel.CancelEventArgs(); OnBeforeAutoHide(e); if (!e.Cancel) { timer.Stop(); } } else { timer.Stop(); } } void timer_Tick(object sender , EventArgs e) { if (MenuSelectedOrDropedDown) { leaveCheck = false; } else if (leaveCheck == false) { //念のためタイマーで2回チェック leaveCheck = true; } else { //メニュー操作が終わったとみなしたら監視を停止してHide StopTimer(); } } /// <summary>メニュー選択中かどうか</summary> [System.ComponentModel.Browsable(false)] public bool MenuSelectedOrDropedDown { get { bool retval = false; foreach (ToolStripItem item in this.Items) { if (item.Selected) { retval = true; break; } ToolStripDropDownItem dropDownItem; dropDownItem = item as ToolStripDropDownItem; if (dropDownItem != null) { if (dropDownItem.DropDown != null && dropDownItem.DropDown.Visible) { retval = true; break; } } } return retval; } } /// <summary>自動的に非表示になる前に問い合わせするイベント</summary> public event System.ComponentModel.CancelEventHandler BeforeAutoHide; protected void OnBeforeAutoHide(System.ComponentModel.CancelEventArgs e) { if (BeforeAutoHide != null) { BeforeAutoHide(this , e); if (!e.Cancel) { this.Visible = false; } } } protected override void Dispose(bool disposing) { if (disposing) { if (timer != null) { StopTimer(); timer.Dispose(); } } base.Dispose(disposing); } }
- 回答としてマーク kanryu 2010年2月4日 2:21
すべての返信
-
言語が何かわからないのでC#で書いてみた
/// <summary>自動的に非表示になるMenuStrip</summary> public class AutoHideMenuStrip : MenuStrip { public AutoHideMenuStrip() : base() { timer = new Timer(); timer.Interval = 500; timer.Tick += new EventHandler(timer_Tick); } /// <summary>自動非表示するか</summary> public bool AutoHide { get { return _AutoHide; } set { _AutoHide = value; StopTimer(); } } private bool _AutoHide = false; /// <summary>自動非表示の監視時間</summary> public int AutoHideTime { get { return timer.Interval; } set { timer.Interval = value; } } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); if (this.Visible && !this.DesignMode) { StartTimer(); } } protected override void OnMenuDeactivate(EventArgs e) { base.OnMenuDeactivate(e); StopTimer(); } private System.Windows.Forms.Timer timer; private bool leaveCheck = false; private void StartTimer() { if (!this.DesignMode) { leaveCheck = false; timer.Start(); } } private void StopTimer() { if (AutoHide) { System.ComponentModel.CancelEventArgs e = new System.ComponentModel.CancelEventArgs(); OnBeforeAutoHide(e); if (!e.Cancel) { timer.Stop(); } } else { timer.Stop(); } } void timer_Tick(object sender , EventArgs e) { if (MenuSelectedOrDropedDown) { leaveCheck = false; } else if (leaveCheck == false) { //念のためタイマーで2回チェック leaveCheck = true; } else { //メニュー操作が終わったとみなしたら監視を停止してHide StopTimer(); } } /// <summary>メニュー選択中かどうか</summary> [System.ComponentModel.Browsable(false)] public bool MenuSelectedOrDropedDown { get { bool retval = false; foreach (ToolStripItem item in this.Items) { if (item.Selected) { retval = true; break; } ToolStripDropDownItem dropDownItem; dropDownItem = item as ToolStripDropDownItem; if (dropDownItem != null) { if (dropDownItem.DropDown != null && dropDownItem.DropDown.Visible) { retval = true; break; } } } return retval; } } /// <summary>自動的に非表示になる前に問い合わせするイベント</summary> public event System.ComponentModel.CancelEventHandler BeforeAutoHide; protected void OnBeforeAutoHide(System.ComponentModel.CancelEventArgs e) { if (BeforeAutoHide != null) { BeforeAutoHide(this , e); if (!e.Cancel) { this.Visible = false; } } } protected override void Dispose(bool disposing) { if (disposing) { if (timer != null) { StopTimer(); timer.Dispose(); } } base.Dispose(disposing); } }
- 回答としてマーク kanryu 2010年2月4日 2:21
-
ご回答ありがとうございます。
しかも動作するサンプルコードまでつけてくださって。
早速組み込んでみましたが、このトピックで挙げた不具合はサンプルコード中の
タイマー処理が動作することで見事解消することが確認できました。
MenuStripの表示切替は親フォーム側で行っているため
Control側のサブクラス化を伴うこのサンプルコードを
そのまま組み合わせるべきか微妙なところも残っていますが、
アプリケーションの挙動としてはこれで問題ありません。
以前のトピック のこともあり、どうもSystem.Windows.FormsのMenuStripは、
細かいところで作り込みが足りないところがあるような気がしますね。。。
ともあれ、たいへんありがとうございました!