The blog of Eric Sibly; focusing on mountain biking, .NET development for the Desktop, Smartphone and PocketPC.

Wednesday, February 02, 2005

.NET CF PopupManager...

This is where I actually post some real code for the first time :-) Basically I have created a PopupManager that will take a control and display it over a selected form on the PDA stopping stylus input to the underlying form. For the Popup you can choose to display it centered, display it with an optional border, and have it automatically hide if the user clicks outside of the popup control. The code is as-is where-is, use at your own risk, royalty free; if you have any comments etc. then please let me know. Note: you will need to be using OpenNETCF for this to work, see the class documentation within the code below for more information.

And finally, here is the code (excuse the line breaks) - enjoy!

using System;
using System.Drawing;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
using Microsoft.WindowsCE.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Chullybun.NetCF
{
  /// <summary>
  /// A class for managing a <see cref="Popup"/> control within the context of an <see cref="Owner"/> form.
  /// </summary>
  /// <remarks>For this funtionality to work it needs to hook into the standard Windows messages through the use of message
  /// filters (<see cref="IMessageFilter"/>). Unfortunately, within the standard .NET Compact Framework this is not implemented,
  /// but all is not lost - the fantastic folks over at OpenNETCF (http://www.opennetcf.org) have provided the <see cref="ApplicationEx"/>
  /// class that provides this functionality. So to use the <see cref="PopupManager"/> the <see cref="ApplicationEx.Run"/> method
  /// should be used to run the applications main form.
  /// </remarks>
  public sealed class PopupManager
  {
    private Form owner;
    private Control popup;
    private Panel panel;
    private PopupMessageFilter filter;
    private bool centered = true;
    private bool bordered = false;
    private bool autoHide = false;

    /// <summary>
    /// Initializes a new instance of the <see cref="PopupManager"/> class.
    /// </summary>
    /// <param name="owner"><see cref="Owner"/> form.</param>
    /// <param name="popup"><see cref="Popup"/> control.</param>
    public PopupManager(Form owner, Control popup)
    {
      if (owner == null)
        throw new ArgumentNullException("owner");

      if (popup == null)
        throw new ArgumentNullException("popup");

      this.owner = owner;
      this.popup = popup;
    }

    #region Properties

    /// <summary>
    /// Gets the <see cref="Form"/> that owns the <see cref="Popup"/>.
    /// </summary>
    public Form Owner
    {
      get { return owner; }
    }

    /// <summary>
    /// Gets the <see cref="Popup"/> that will be displayed.
    /// </summary>
    public Control Popup
    {
      get { return popup; }
    }

    /// <summary>
    /// Indicates whether the <see cref="Popup"/> should be centered over the <see cref="Owner"/> form.
    /// </summary>
    /// <remarks>The default value is <b>true</b>.</remarks>
    public bool Centered
    {
      get { return centered; }
      set { centered = value; }
    }

    /// <summary>
    /// Indicates whether a border line will be displayed around <see cref="Popup"/>.
    /// </summary>
    /// <remarks>The default value is <b>false</b>.</remarks>
    public bool Bordered
    {
      get { return bordered; }
      set { bordered = value; }
    }

    /// <summary>
    /// Indicates whether the <see cref="Popup"/> should automatically <see cref="Hide"/> when a mouse event occurs outside of the popup region.
    /// </summary>
    public bool AutoHide
    {
      get { return autoHide; }
      set { autoHide = value; }
    }

    #endregion

    #region Show/Hide

    /// <summary>
    /// Show the <see cref="Popup"/>.
    /// </summary>
    public void Show()
    {
      // Make sure the popup hasn't already been associated with another form.
      if (popup.Parent != null)
        throw new InvalidOperationException("Control has already been parented to another control or is already displayed.");

      // Center the popup where requested to do so.
      if (centered)
      {
        popup.Left = (owner.ClientRectangle.Width - popup.Width) / 2;
        popup.Top = (owner.ClientRectangle.Height - popup.Height) / 2;
      }

      // Add the filter so we can filter mouse messages from the rest of the form.
      if (filter == null)
        filter = new PopupMessageFilter(this);

      ApplicationEx.AddMessageFilter(filter);

      // Add a bordered panel if requested.
      if (bordered)
      {
        panel = new Panel();
        panel.Top = popup.Top - 1;
        panel.Left = popup.Left - 1;
        panel.Width = popup.Width + 2;
        panel.Height = popup.Height + 2;
        panel.BackColor = Color.Black;

        owner.Controls.Add(panel);
        owner.Controls.SetChildIndex(panel, 0);
      }

      // Add the popup to the owning form, make it the foremost control and set its focus.
      owner.Controls.Add(popup);
      owner.Controls.SetChildIndex(popup, 0);
      popup.Focus();
    }

    /// <summary>
    /// Hides the <see cref="Popup"/>.
    /// </summary>
    public void Hide()
    {
      // Make sure the popup is assigned as expected.
      if (popup.Parent == null popup.Parent != owner)
        return;

      // Remove the message filter and control from owner form.
      ApplicationEx.RemoveMessageFilter(filter);
      owner.Controls.Remove(popup);

      // Remove the bordered panel where previously added.
      if (panel != null)
      {
        owner.Controls.Remove(panel);
        panel.Dispose();
        panel = null;
      }
    }

    /// <summary>
    /// Action the automatic hide of the popup.
    /// </summary>
    private void ActionAutoHide()
    {
      CancelEventArgs e = new CancelEventArgs(false);
      if (PopupAutoHiding != null)
        PopupAutoHiding(this, e);

      if (!e.Cancel)
        Hide();
    }

    #endregion

    #region Events

    /// <summary>
    /// Occurs when the <see cref="Owner"/> receives a close message.
    /// </summary>
    public event CancelEventHandler OwnerClosing;

    /// <summary>
    /// Occurs when the <see cref="Popup"/> is about to automatically hide.
    /// </summary>
    public event CancelEventHandler PopupAutoHiding;

    #endregion

    #region PopupMessageFilter

    /// <summary>
    /// Class for filtering windows messages.
    /// </summary>
    private class PopupMessageFilter : IMessageFilter
    {
      private const int WM_CLOSE = 0x0010;
      private const int WM_LBUTTONDOWN = 0x0201;
      private const int WM_LBUTTONUP = 0x0202;
      private const int WM_LBUTTONDBLCLK = 0x0203;

      [DllImport("coredll.dll")]
      private static extern bool GetCursorPos(out POINT lpPoint);

      [StructLayout(LayoutKind.Sequential)]
      struct POINT
      {
        public int X;
        public int Y;
      }

      private PopupManager pm;
      private bool hidePopupNextMessage = false;

      /// <summary>
      /// Initializes a new instance of the class.
      /// </summary>
      public PopupMessageFilter(PopupManager pm)
      {
        this.pm = pm;
      }

      /// <summary>
      /// Filters messages before they are sent to the form proper.
      /// </summary>
      public bool PreFilterMessage(ref Message m)
      {
        // Hide the popup
        if (hidePopupNextMessage)
        {
          hidePopupNextMessage = false;
          pm.ActionAutoHide();
        }

        switch (m.Msg)
        {
          case WM_LBUTTONDOWN:
          case WM_LBUTTONUP:
          case WM_LBUTTONDBLCLK:
            // Swallows all the messages relating to the left mouse button, i.e. the stylus.
            POINT pt;
            if (GetCursorPos(out pt))
            {
              bool inPopup = pm.Popup.RectangleToScreen(pm.Popup.ClientRectangle).Contains(pt.X, pt.Y);

              // Where selected to auto hide do so on next message as current needs to be swallowed before we attempt to hide.
              if (pm.autoHide && !inPopup)
                hidePopupNextMessage = true;

              return !inPopup;
            }

            break;

          case WM_CLOSE:
            // Provides a means for the consumer to stop the form close.
            CancelEventArgs e = new CancelEventArgs(false);
            if (pm.OwnerClosing != null)
              pm.OwnerClosing(pm, e);

            if (e.Cancel)
              return true;

            break;
        }

        return false;
      }
    }
    #endregion
  }
}

0 Comments:

Post a Comment

<< Home