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
}
}