using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; namespace AT.STO.UI.Win { /// /// A class to assist in creating popup windows like Combo Box drop-downs and Menus. /// This class includes functionality to keep the title bar of the popup owner form /// active whilst the popup is displayed, and to automatically cancel the popup /// whenever the user clicks outside the popup window or shifts focus to another /// application. /// /// Thousand thanks to Steve McMahon: /// http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/Popup_Form_Demonstration.asp /// internal class DropDownWindowHelper : NativeWindow { #region Private Variable Declarations private EventHandler DropDownClosedHandler = null; private Form _dropDown = null; private bool _dropDownShowing = false; private DropDownMessageFilter _filter = null; private Form _owner = null; private bool _skipClose = false; #endregion #region Event Declarations public event DropDownCancelEventHandler DropDownCancel; public event DropDownClosedEventHandler DropDownClosed; #endregion #region Constructor / Destructor /// /// Default constructor. /// /// Use the /// method to attach this class to the form you want to show popups from. public DropDownWindowHelper() { _filter = new DropDownMessageFilter(this); _filter.DropDownCancel += new DropDownCancelEventHandler(Popup_Cancel); } #endregion #region Event Handler private void Popup_Cancel(object sender, DropDownCancelEventArgs e) { OnDropDownCancel(e); } /// /// Responds to the /// event from the popup form. /// /// Popup form that has been closed. /// Not used. private void Popup_Closed(object sender, EventArgs e) { CloseDropDown(); } /// /// Subclasses the owning form's existing Window Procedure to enables the /// title bar to remain active when a popup is show, and to detect if /// the user clicks onto another application whilst the popup is visible. /// /// Window Procedure Message protected override void WndProc(ref Message m) { base.WndProc(ref m); if (DropDownShowing) { if (m.Msg == UIApiCalls.WM_NCACTIVATE) { if (((int)m.WParam) == 0) // Check if the title bar will made inactive: { // Note it's no good to try and consume this message; if you try to do that you'll end up with windows UIApiCalls.SendMessage(this.Handle, UIApiCalls.WM_NCACTIVATE, 1, IntPtr.Zero); // If so reactivate it. } } else if (m.Msg == UIApiCalls.WM_ACTIVATEAPP) { if ((int)m.WParam == 0) // Check if the application is being deactivated. { CloseDropDown(); // It is so cancel the popup: UIApiCalls.PostMessage(this.Handle, UIApiCalls.WM_NCACTIVATE, 0, IntPtr.Zero); // And put the title bar into the inactive state: } } } } #endregion #region Public Methods /// /// Shows the specified Form as a popup window, keeping the /// Owner's title bar active and preparing to cancel the popup /// should the user click anywhere outside the popup window. /// Typical code to use this message is as follows: /// /// frmPopup popup = new frmPopup(); /// Point location = this.PointToScreen(new Point(button1.Left, button1.Bottom)); /// popupHelper.ShowPopup(this, popup, location); /// /// Put as much initialisation code as possible /// into the popup form's constructor, rather than the /// event as this will improve visual appearance. /// /// Main form which owns the popup /// Window to show as a popup /// Location relative to the screen to show the popup at. public void ShowDropDown(Form Owner, Form DropDown, Point Location) { _owner = Owner; _dropDown = DropDown; Application.AddMessageFilter(_filter); // Start checking for the popup being cancelled DropDown.StartPosition = FormStartPosition.Manual; // Set the location of the popup form: DropDown.Location = Location; Owner.AddOwnedForm(DropDown); // Make it owned by the window that's displaying it: DropDownClosedHandler = new EventHandler(Popup_Closed); // Respond to the Closed event in case the popup is closed by its own internal means DropDown.Closed += DropDownClosedHandler; _dropDownShowing = true; // Show the popup: DropDown.Show(); DropDown.Activate(); // A little bit of fun. We've shown the popup, but because we've kept the main window's // title bar in focus the tab sequence isn't quite right. This can be fixed by sending a tab, // but that on its own would shift focus to the second control in the form. So send a tab, // followed by a reverse-tab. UIApiCalls.keybd_event((byte) Keys.Tab, 0, 0, 0); UIApiCalls.keybd_event((byte) Keys.Tab, 0, UIApiCalls.KEYEVENTF_KEYUP, 0); UIApiCalls.keybd_event((byte) Keys.ShiftKey, 0, 0, 0); UIApiCalls.keybd_event((byte) Keys.Tab, 0, 0, 0); UIApiCalls.keybd_event((byte) Keys.Tab, 0, UIApiCalls.KEYEVENTF_KEYUP, 0); UIApiCalls.keybd_event((byte) Keys.ShiftKey, 0, UIApiCalls.KEYEVENTF_KEYUP, 0); _filter.DropDown = DropDown; // Start filtering for mouse clicks outside the popup } /// /// Called when the popup is being hidden. /// public void CloseDropDown() { if (DropDownShowing) { if (!_skipClose) { OnPDropDownClosed(new DropDownClosedEventArgs(_dropDown)); } _skipClose = false; _owner.RemoveOwnedForm(_dropDown); // Make sure the popup is closed and we've cleaned up: _dropDownShowing = false; _dropDown.Closed -= DropDownClosedHandler; DropDownClosedHandler = null; _dropDown.Close(); Application.RemoveMessageFilter(_filter); // No longer need to filter for clicks outside the popup. // If we did something from the popup which shifted focus to a new form, like showing another popup // or dialog, then Windows won't know how to bring the original owner back to the foreground, so // force it here: _owner.Activate(); _dropDown = null; // Null out references for GC _owner = null; } } #endregion #region Public Properties /// /// Indicator weither the DropDown is showing. /// public bool DropDownShowing { get { return _dropDownShowing; } } #endregion #region Event Implementation protected virtual void OnDropDownCancel(DropDownCancelEventArgs e) { if (this.DropDownCancel != null) { this.DropDownCancel(this, e); if (!e.Cancel) { _skipClose = true; } } } protected virtual void OnPDropDownClosed(DropDownClosedEventArgs e) { if (this.DropDownClosed != null) { this.DropDownClosed(this, e); } } #endregion } }