.net2.0+ Winform项目实现弹出容器层

适用于:.net2.0+ Winform项目

背景:

有时候我们需要开一个简单的窗口来做一些事,例如输入一些东西、点选一个item之类的,可能像这样:

完了返回原窗体并获取刚刚的输入,这样做并没有什么问题,但在几天前我突然产生了一些想法:为什么非得有板有眼的弹出一个窗体给用户呢,是不是可以在按钮附近迅速呈现一个层来做这些事呢,类似快捷菜单那样,用户高兴就在里面做一下该做的事,不高兴就在其它地方点一下它就消失,本来很轻便快捷的操作,DUANG~弹出一个窗体来会不会令用户心里咯噔一下呢,感受层面的事情往往是很微妙的,不管怎样,我既然起了这个念头,just try it。

我首先找了一下现成的方案,果然在牛逼的codeproject.com已经有牛人做了这样的事情:

http://www.codeproject.com/Articles/17502/Simple-Popup-Control

简单体验了一下,的确是了不起的创造。原理是利用ToolStripControlHost可以承载自定义控件的这一能力,让下拉式控件ToolStripDropDown将任何自定义控件像右键菜单那样弹出来(别忘了右键菜单ContextMenuStrip就是继承自ToolStripDropDown),这样就等于把菜单作为一个容器,可以弹出任何或简单或复杂的控件组合,同时又具有菜单具有的便捷性,召之即来挥之即去。当时了解到这方案的时候真挺开心,正是我想要的效果,感觉这下好了,不用瞎费劲自己造了。

但很快发现一个在我看来还挺在意的不足,就是ToolStripDropDown只有Show,没有ShowDialog,就是不能以模式化(Modal,也有叫模态的,鉴于MSDN都称模式,我也随流叫它模式)的方式弹出,这是由ToolStripDropDown的固有能力决定的,该方案既然基于ToolStripDropDown,自然也受限于此,不能模式化弹出。这样带来的问题是某些情况下的调用体验不好(体验这种事当然不是用户才有的专利,俺们码农也是人,也要讲体验的说),比如弹出的控件是让用户输入一些东西,完了用户点击某个按钮什么的返回原窗体,然后在原窗体获取用户刚刚的输入,然后接着做后面的事。由于非模式的Show不会阻塞代码,所以就不能在Show的下方想当然的获取值、使用值~这是显然的。要想获得值可能就得额外采取一些做法,例如响应弹出控件的关闭事件,或者把原窗体传入弹出控件完了在后者中做原本应该在原窗体中做的事~等等,办法当然有很多,但这都是因为只能Show带来的多余的事,有什么比在一个方法中弹出控件、等待返回、继续处理来的爽滑的呢,像这样不是很自然吗:

代码如下:

string s;using (Popup p = new Popup()){ if (p.ShowDialog() != DialogResult.OK) { return; } s = p.InputText;}//go on...

所以很遗憾,不得不挥别这个优秀的方案,造自己的轮子。不过受该方案的启发,我想到用ContextMenu来做容器(注意这个菜单类跟上面提到的继承自ToolStripDropDown的ContextMenuStrip大大的不同,前者是OS原生的菜单,就是在桌面、图标以及文本框中右键弹出的那种菜单,.net是通过调API的方式来操作这样的菜单,而后者则完全是.net实现,更多信息请参考MSDN,此处不展开),因为ContextMenu的Show是阻塞式的,正合我意。但一番尝试之后放弃,它的菜单项MenuItem不像ToolStripItem那样可以通过ToolStripControlHost承载自定义控件,希望是我能力有限,总之我做不到把自定义控件弄到ContextMenu上,也没见过原生菜单上出现过文本框、复选框等奇怪的东西,如果您知道怎么扩展原生菜单,还望不吝赐教,先行谢过!

我还是打回.net的主意,当中仍然是做了许多不同的尝试,Form、Panel、UserControl、ContainerControl、Control等等看起来适合做容器层的东西都试了个遍,甚至重新在ToolStripDropDown上打主意,最后选用Form,改造一番,自我感觉较理想的实现了我要的东西:一个叫做FloatLayerBase的基类,它本身继承自System.Windows.Forms.Form类,而需要作为浮动层显示的应用则继承自FloatLayerBase进行实现,例如下面这个接受用户输入数值的NumInputDemo实现:

样子和特点:不会令父窗口失去焦点(不会抢焦点的层才是好层):

当然,男人不止一面:

还有其它边框样式,有待用户自行体验,最后有demo提供。

可以有调整尺寸的手柄:

可以点住客户区拖动:

别的一些应用:

这些都只是demo,没那么好看和强大,重点是有了这个FloatLayerBase,就可以实现自己的浮动应用。

使用说明:确保FloatLayerBase类在项目中~废话。源码在此:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace AhDung.WinForm.Controls
{
  /// <summary>
  /// 浮动层基类
  /// </summary>
  public class FloatLayerBase : Form
  {
    /// <summary>
    /// 鼠标消息筛选器
    /// </summary>
    //由于本窗体为WS_CHILD,所以不会收到在窗体以外点击鼠标的消息
    //该消息筛选器的作用就是让本窗体获知鼠标点击情况,进而根据鼠标是否在本窗体以外的区域点击,做出相应处理
    readonly AppMouseMessageHandler _mouseMsgFilter;

    /// <summary>
    /// 指示本窗体是否已ShowDialog过
    /// </summary>
    //由于多次ShowDialog会使OnLoad/OnShown重入,故需设置此标记以供重入时判断
    bool _isShowDialogAgain;

    //边框相关字段
    BorderStyle _borderType;
    Border3DStyle _border3DStyle;
    ButtonBorderStyle _borderSingleStyle;
    Color _borderColor;

    /// <summary>
    /// 获取或设置边框类型
    /// </summary>
    [Description("获取或设置边框类型。")]
    [DefaultValue(BorderStyle.Fixed3D)]
    public BorderStyle BorderType
    {
      get { return _borderType; }
      set
      {
        if (_borderType == value) { return; }
        _borderType = value;
        Invalidate();
      }
    }

    /// <summary>
    /// 获取或设置三维边框样式
    /// </summary>
    [Description("获取或设置三维边框样式。")]
    [DefaultValue(Border3DStyle.RaisedInner)]
    public Border3DStyle Border3DStyle
    {
      get { return _border3DStyle; }
      set
      {
        if (_border3DStyle == value) { return; }
        _border3DStyle = value;
        Invalidate();
      }
    }

    /// <summary>
    /// 获取或设置线型边框样式
    /// </summary>
    [Description("获取或设置线型边框样式。")]
    [DefaultValue(ButtonBorderStyle.Solid)]
    public ButtonBorderStyle BorderSingleStyle
    {
      get { return _borderSingleStyle; }
      set
      {
        if (_borderSingleStyle == value) { return; }
        _borderSingleStyle = value;
        Invalidate();
      }
    }

    /// <summary>
    /// 获取或设置边框颜色(仅当边框类型为线型时有效)
    /// </summary>
    [Description("获取或设置边框颜色(仅当边框类型为线型时有效)。")]
    [DefaultValue(typeof(Color), "DarkGray")]
    public Color BorderColor
    {
      get { return _borderColor; }
      set
      {
        if (_borderColor == value) { return; }
        _borderColor = value;
        Invalidate();
      }
    }

    protected override sealed CreateParams CreateParams
    {
      get
      {
        CreateParams prms = base.CreateParams;

        //prms.Style = 0;
        //prms.Style |= -2147483648;  //WS_POPUP
        prms.Style |= 0x40000000;   //WS_CHILD 重要,只有CHILD窗体才不会抢父窗体焦点
        prms.Style |= 0x4000000;    //WS_CLIPSIBLINGS
        prms.Style |= 0x10000;     //WS_TABSTOP
        prms.Style &= ~0x40000;    //WS_SIZEBOX    去除
        prms.Style &= ~0x800000;    //WS_BORDER    去除
        prms.Style &= ~0x400000;    //WS_DLGFRAME   去除
        //prms.Style &= ~0x20000;   //WS_MINIMIZEBOX  去除
        //prms.Style &= ~0x10000;   //WS_MAXIMIZEBOX  去除

        prms.ExStyle = 0;
        //prms.ExStyle |= 0x1;     //WS_EX_DLGMODALFRAME 立体边框
        //prms.ExStyle |= 0x8;     //WS_EX_TOPMOST
        prms.ExStyle |= 0x10000;    //WS_EX_CONTROLPARENT
        //prms.ExStyle |= 0x80;    //WS_EX_TOOLWINDOW
        //prms.ExStyle |= 0x100;    //WS_EX_WINDOWEDGE
        //prms.ExStyle |= 0x8000000;  //WS_EX_NOACTIVATE
        //prms.ExStyle |= 0x4;     //WS_EX_NOPARENTNOTIFY

        return prms;
      }
    }

    //构造函数
    public FloatLayerBase()
    {
      //初始化消息筛选器。添加和移除在显示/隐藏时负责
      _mouseMsgFilter = new AppMouseMessageHandler(this);

      //初始化基类属性
      InitBaseProperties();

      //初始化边框相关
      _borderType = BorderStyle.Fixed3D;
      _border3DStyle = System.Windows.Forms.Border3DStyle.RaisedInner;
      _borderSingleStyle = ButtonBorderStyle.Solid;
      _borderColor = Color.DarkGray;
    }

    protected override void OnLoad(EventArgs e)
    {
      //防止重入
      if (_isShowDialogAgain) { return; }

      //需得减掉两层边框宽度,运行时尺寸才与设计时完全相符,原因不明
      //确定与ControlBox、FormBorderStyle有关,但具体联系不明
      if (!DesignMode)
      {
        Size size = SystemInformation.FrameBorderSize;
        this.Size -= size + size;//不可以用ClientSize,后者会根据窗口风格重新调整Size
      }
      base.OnLoad(e);
    }

    protected override void OnShown(EventArgs e)
    {
      //防止重入
      if (_isShowDialogAgain) { return; }

      //在OnShown中为首次ShowDialog设标记
      if (Modal) { _isShowDialogAgain = true; }

      if (!DesignMode)
      {
        //激活首控件
        Control firstControl;
        if ((firstControl = GetNextControl(this, true)) != null)
        {
          firstControl.Focus();
        }
      }
      base.OnShown(e);
    }

    protected override void WndProc(ref Message m)
    {
      //当本窗体作为ShowDialog弹出时,在收到WM_SHOWWINDOW前,Owner会被Disable
      //故需在收到该消息后立即Enable它,不然Owner窗体和本窗体都将处于无响应状态
      if (m.Msg == 0x18 && m.WParam != IntPtr.Zero && m.LParam == IntPtr.Zero
        && Modal && Owner != null && !Owner.IsDisposed)
      {
        if (Owner.IsMdiChild)
        {
          //当Owner是MDI子窗体时,被Disable的是MDI主窗体
          //并且Parent也会指向MDI主窗体,故需改回为Owner,这样弹出窗体的Location才会相对于Owner而非MDIParent
          NativeMethods.EnableWindow(Owner.MdiParent.Handle, true);
          NativeMethods.SetParent(this.Handle, Owner.Handle);//只能用API设置Parent,因为模式窗体是TopLevel,.Net拒绝为顶级窗体设置Parent
        }
        else
        {
          NativeMethods.EnableWindow(Owner.Handle, true);
        }
      }
      base.WndProc(ref m);
    }

    //画边框
    protected override void OnPaintBackground(PaintEventArgs e)
    {
      base.OnPaintBackground(e);

      if (_borderType == BorderStyle.Fixed3D)//绘制3D边框
      {
        ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle);
      }
      else if (_borderType == BorderStyle.FixedSingle)//绘制线型边框
      {
        ControlPaint.DrawBorder(e.Graphics, ClientRectangle, BorderColor, BorderSingleStyle);
      }
    }

    //显示后添加鼠标消息筛选器以开始捕捉,隐藏时则移除筛选器。之所以不放Dispose中是想尽早移除筛选器
    protected override void OnVisibleChanged(EventArgs e)
    {
      if (!DesignMode)
      {
        if (Visible) { Application.AddMessageFilter(_mouseMsgFilter); }
        else { Application.RemoveMessageFilter(_mouseMsgFilter); }
      }
      base.OnVisibleChanged(e);
    }

    //实现窗体客户区拖动
    //在WndProc中实现这个较麻烦,所以放到这里做
    protected override void OnMouseDown(MouseEventArgs e)
    {
      //让鼠标点击客户区时达到与点击标题栏一样的效果,以此实现客户区拖动
      NativeMethods.ReleaseCapture();
      NativeMethods.SendMessage(Handle, 0xA1/*WM_NCLBUTTONDOWN*/, (IntPtr)2/*CAPTION*/, IntPtr.Zero);

      base.OnMouseDown(e);
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="control">显示在该控件下方</param>
    public DialogResult ShowDialog(Control control)
    {
      return ShowDialog(control, 0, control.Height);
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="control">触发弹出窗体的控件</param>
    /// <param name="offsetX">相对control水平偏移</param>
    /// <param name="offsetY">相对control垂直偏移</param>
    public DialogResult ShowDialog(Control control, int offsetX, int offsetY)
    {
      return ShowDialog(control, new Point(offsetX, offsetY));
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="control">触发弹出窗体的控件</param>
    /// <param name="offset">相对control偏移</param>
    public DialogResult ShowDialog(Control control, Point offset)
    {
      return this.ShowDialogInternal(control, offset);
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="item">显示在该工具栏项的下方</param>
    public DialogResult ShowDialog(ToolStripItem item)
    {
      return ShowDialog(item, 0, item.Height);
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="item">触发弹出窗体的工具栏项</param>
    /// <param name="offsetX">相对item水平偏移</param>
    /// <param name="offsetY">相对item垂直偏移</param>
    public DialogResult ShowDialog(ToolStripItem item, int offsetX, int offsetY)
    {
      return ShowDialog(item, new Point(offsetX, offsetY));
    }

    /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="item">触发弹出窗体的工具栏项</param>
    /// <param name="offset">相对item偏移</param>
    public DialogResult ShowDialog(ToolStripItem item, Point offset)
    {
      return this.ShowDialogInternal(item, offset);
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="control">显示在该控件下方</param>
    public void Show(Control control)
    {
      Show(control, 0, control.Height);
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="control">触发弹出窗体的控件</param>
    /// <param name="offsetX">相对control水平偏移</param>
    /// <param name="offsetY">相对control垂直偏移</param>
    public void Show(Control control, int offsetX, int offsetY)
    {
      Show(control, new Point(offsetX, offsetY));
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="control">触发弹出窗体的控件</param>
    /// <param name="offset">相对control偏移</param>
    public void Show(Control control, Point offset)
    {
      this.ShowInternal(control, offset);
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="item">显示在该工具栏下方</param>
    public void Show(ToolStripItem item)
    {
      Show(item, 0, item.Height);
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="item">触发弹出窗体的工具栏项</param>
    /// <param name="offsetX">相对item水平偏移</param>
    /// <param name="offsetY">相对item垂直偏移</param>
    public void Show(ToolStripItem item, int offsetX, int offsetY)
    {
      Show(item, new Point(offsetX, offsetY));
    }

    /// <summary>
    /// 显示窗体
    /// </summary>
    /// <param name="item">触发弹出窗体的工具栏项</param>
    /// <param name="offset">相对item偏移</param>
    public void Show(ToolStripItem item, Point offset)
    {
      this.ShowInternal(item, offset);
    }

    /// <summary>
    /// ShowDialog内部方法
    /// </summary>
    private DialogResult ShowDialogInternal(Component controlOrItem, Point offset)
    {
      //快速连续弹出本窗体将有可能遇到尚未Hide的情况下再次弹出,这会引发异常,故需做处理
      if (this.Visible) { return System.Windows.Forms.DialogResult.None; }

      this.SetLocationAndOwner(controlOrItem, offset);
      return base.ShowDialog();
    }

    /// <summary>
    /// Show内部方法
    /// </summary>
    private void ShowInternal(Component controlOrItem, Point offset)
    {
      if (this.Visible) { return; }//原因见ShowDialogInternal

      this.SetLocationAndOwner(controlOrItem, offset);
      base.Show();
    }

    /// <summary>
    /// 设置坐标及所有者
    /// </summary>
    /// <param name="controlOrItem">控件或工具栏项</param>
    /// <param name="offset">相对偏移</param>
    private void SetLocationAndOwner(Component controlOrItem, Point offset)
    {
      Point pt = Point.Empty;

      if (controlOrItem is ToolStripItem)
      {
        ToolStripItem item = (ToolStripItem)controlOrItem;
        pt.Offset(item.Bounds.Location);
        controlOrItem = item.Owner;
      }

      Control c = (Control)controlOrItem;
      pt.Offset(GetControlLocationInForm(c));
      pt.Offset(offset);
      this.Location = pt;

      //设置Owner属性与Show[Dialog](Owner)有不同,当Owner是MDIChild时,后者会改Owner为MDIParent
      this.Owner = c.FindForm();
    }

    /// <summary>
    /// 获取控件在窗体中的坐标
    /// </summary>
    private static Point GetControlLocationInForm(Control c)
    {
      Point pt = c.Location;
      while (!((c = c.Parent) is Form))
      {
        pt.Offset(c.Location);
      }
      return pt;
    }

    #region 屏蔽对本类影响重大的基类方法和属性

    /// <summary>
    /// 初始化部分基类属性
    /// </summary>
    private void InitBaseProperties()
    {
      base.ControlBox = false;              //重要
      //必须得是SizableToolWindow才能支持调整大小的同时,不受SystemInformation.MinWindowTrackSize的限制
      base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
      base.Text = string.Empty;             //重要
      base.HelpButton = false;
      base.Icon = null;
      base.IsMdiContainer = false;
      base.MaximizeBox = false;
      base.MinimizeBox = false;
      base.ShowIcon = false;
      base.ShowInTaskbar = false;
      base.StartPosition = FormStartPosition.Manual;   //重要
      base.TopMost = false;
      base.WindowState = FormWindowState.Normal;
    }

    //屏蔽原方法
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("请使用别的重载!", true)]
    public new DialogResult ShowDialog() { throw new NotImplementedException(); }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("请使用别的重载!", true)]
    public new DialogResult ShowDialog(IWin32Window owner) { throw new NotImplementedException(); }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("请使用别的重载!", true)]
    public new void Show() { throw new NotImplementedException(); }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("请使用别的重载!", true)]
    public new void Show(IWin32Window owner) { throw new NotImplementedException(); }

    //屏蔽原属性
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool ControlBox { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("设置边框请使用Border相关属性!", true)]
    public new FormBorderStyle FormBorderStyle { get { return System.Windows.Forms.FormBorderStyle.SizableToolWindow; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public override sealed string Text { get { return string.Empty; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool HelpButton { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new Image Icon { get { return null; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool IsMdiContainer { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool MaximizeBox { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool MinimizeBox { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool ShowIcon { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool ShowInTaskbar { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new FormStartPosition StartPosition { get { return FormStartPosition.Manual; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new bool TopMost { get { return false; } set { } }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("禁用该属性!", true)]
    public new FormWindowState WindowState { get { return FormWindowState.Normal; } set { } }

    #endregion

    /// <summary>
    /// 程序鼠标消息筛选器
    /// </summary>
    private class AppMouseMessageHandler : IMessageFilter
    {
      readonly FloatLayerBase _layerForm;

      public AppMouseMessageHandler(FloatLayerBase layerForm)
      {
        _layerForm = layerForm;
      }

      public bool PreFilterMessage(ref Message m)
      {
        //如果在本窗体以外点击鼠标,隐藏本窗体
        //若想在点击标题栏、滚动条等非客户区也要让本窗体消失,取消0xA1的注释即可
        //本例是根据坐标判断,亦可以改为根据句柄,但要考虑子孙控件
        //之所以用API而不用Form.DesktopBounds是因为后者不可靠
        if ((m.Msg == 0x201/*|| m.Msg==0xA1*/)
          && _layerForm.Visible && !NativeMethods.GetWindowRect(_layerForm.Handle).Contains(MousePosition))
        {
          _layerForm.Hide();//之所以不Close是考虑应该由调用者负责销毁
        }

        return false;
      }
    }

    /// <summary>
    /// API封装类
    /// </summary>
    private static class NativeMethods
    {
      [DllImport("user32.dll")]
      [return: MarshalAs(UnmanagedType.Bool)]
      public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

      [DllImport("user32.dll", CharSet = CharSet.Auto)]
      public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

      [DllImport("user32.dll")]
      public static extern bool ReleaseCapture();

      [DllImport("user32.dll", SetLastError = true)]
      public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

      [DllImport("user32.dll", SetLastError = true)]
      private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

      [StructLayout(LayoutKind.Sequential)]
      private struct RECT
      {
        public int left;
        public int top;
        public int right;
        public int bottom;

        public static explicit operator Rectangle(RECT rect)
        {
          return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
        }
      }

      public static Rectangle GetWindowRect(IntPtr hwnd)
      {
        RECT rect;
        GetWindowRect(hwnd, out rect);
        return (Rectangle)rect;
      }
    }
  }
}

新建继承窗体,选择继承自FloatLayerBase类;也可以新建普通窗体,然后把基类由Form改为FloatLayerBase
在设计器和源码中打造浮动应用
在需要的地方使用它。关于使用,先看一下FloatLayerBase的部分公开成员:

//属性
public BorderStyle BorderType { get; set; }
public Border3DStyle Border3DStyle { get; set; }
public ButtonBorderStyle BorderSingleStyle { get; set; }
public Color BorderColor { get; set; }

//方法
public void Show(Control control);
public void Show(Control control, Point offset);
public void Show(Control control, int offsetX, int offsetY);
public void Show(ToolStripItem item);
public void Show(ToolStripItem item, Point offset);
public void Show(ToolStripItem item, int offsetX, int offsetY);
public DialogResult ShowDialog(Control control);
public DialogResult ShowDialog(Control control, Point offset);
public DialogResult ShowDialog(Control control, int offsetX, int offsetY);
public DialogResult ShowDialog(ToolStripItem item);
public DialogResult ShowDialog(ToolStripItem item, Point offset);
public DialogResult ShowDialog(ToolStripItem item, int offsetX, int offsetY);

上面4个属性都是跟边框有关的,边框总共有3种形态,三维、线型、无,由BorderType指定;当为三维形态时,由Border3DStyle指定具体样式;为线型时,由BorderSingleStyle和BorderColor分别指定具体线型和颜色。原Form.FormBorderStyle属性已被屏蔽,不允许子类访问,还有若干原Form的属性也已屏蔽,原因都在源码里。另外,原Form.SizeGripStyle照常使用,是否允许调整浮动层大小就靠它了

方法就说一下Show和ShowDialog,显然分别是用来非模式化/模式化显示浮动层的,两者在调用角度的重大区别就是,前者不会阻塞代码,后者则会,实际应用中根据情况选用。每个方法从参数又分Control和ToolStripItem两类,都是代表从什么控件上弹出浮动层的意思,前者接受Button、TextBox等控件(不能传入Form,后果会不愉快),后者接受工具栏上面的项目,例如ToolStripButton、ToolStripTextBox之类的。重载可以指定相对control或item的偏移位置,默认是在control/item的下方弹出浮动层。最后无论是Show还是ShowDialog弹出来的浮动层,都可以像右键菜单那样通过在其它地方点鼠标使之消失,这里需要说明一下:

鼠标只会点在本程序内的窗体中时,让浮动层消失。点在程序外的窗口、桌面、任务栏这些则不会。为什么要这样是因为要做到完全像右键菜单那样对全局鼠标敏感,需要全局钩子,这会增加代码量(性能且不说,没测过不妄言),而且我认为没必要全局敏感浮动层消失是调用Hide方法,所以对于模式化打开的浮动层,会返回DialogResult.Cancel,这是.net对模式对话框的设计使然,模式对话框被Hide或Close时,就是返回Cancel。在此也提醒一下调用者,在使用模式对话框时,永远考虑有返回Cancel这种情况,不限于本例,而是所有对话框

原Show()/Show(IWin32Window)和ShowDialog()/ShowDialog(IWin32Window)已被屏蔽,原因见源码。

其它:

编写期间一直使用PopupFormBase作为类名,发布最后时刻才改为现在的FloatLayerBase,所以demo中可能尚有依据原名起名的子类、方法名等。

Demo下载:

http://pan.baidu.com/s/1mgnGPGc

里面有个Tester供您体验。

(0)

相关推荐

  • c# winform窗口一直置顶显示在桌面最上方或最底层的方法

    一. 在最前面: using System.Runtime.InteropServices; 在定义部分引入下面两个函数: [DllImport( "user32 ")] private static extern IntPtr FindWindow(string lpClassName,string lpWindowName); [DllImport( "user32 ")] private static extern IntPtr SetParent(IntPt

  • winform树形菜单无限级分类实例

    以下实现的是一个树形菜单,每一级对应一类窗口,点击叶子节点时弹出新的窗口: 用递归和委托实现的. 第一部分功能-创建树形菜单 复制代码 代码如下: /// <summary>         /// 创建树形菜单         /// </summary>         public void AddTree(int ParentID, TreeNode pNode)         {             // 数据库名字字段             string str

  • c# winform读取xml文件创建菜单的代码

    复制代码 代码如下: using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using WinformMenu.Helper;using System.Xml; namespace WinformMen

  • win7中C#的winForm编程使用savefiledialog不能弹出保存窗体的解决方法

    本文实例分析了win7中C#的winForm编程使用savefiledialog不能弹出保存窗体的解决方法.分享给大家供大家参考.具体分析如下: 复制代码 代码如下: public void ResMsg() {     while (isRecMsg)     {  //准备一个数组 准备接收 服务端发来的数据  byte[] msgRec = new byte[1024 * 1024 * 2];  //接收服务端发来的数据,此方法也会阻断当前线程,并返回接收的数据的长度  int recLe

  • WinForm特效之桌面上的遮罩层实现方法

    本文实例讲述了WinForm特效之桌面上的遮罩层实现方法,分享给大家供大家参考之用.具体如下: 这个一个窗体特效,可以帮你了解几个windows api函数. 效果:windows桌面上增加一个简单的遮罩层,其中WS_EX_TRANSPARENT 比较重要,它实现了鼠标穿透的功能. 主要功能代码如下: using System; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServic

  • C#实现Winform动态添加菜单的方法

    本文实例讲述了C#实现Winform动态添加菜单的方法.分享给大家供大家参考.具体分析如下: 最近在做WINFORM开发,一直都在为主界面的点击事件及动态加载菜单苦脑.现在已解决这个问题了,可以实现数据库或都XML等配置完成动态生成菜单及事件加载.代码如下: private void Form1_Load(object sender, EventArgs e) { //添加菜单一 ToolStripMenuItem subItem; subItem = AddContextMenu("入库&qu

  • .net2.0+ Winform项目实现弹出容器层

    适用于:.net2.0+ Winform项目 背景: 有时候我们需要开一个简单的窗口来做一些事,例如输入一些东西.点选一个item之类的,可能像这样: 完了返回原窗体并获取刚刚的输入,这样做并没有什么问题,但在几天前我突然产生了一些想法:为什么非得有板有眼的弹出一个窗体给用户呢,是不是可以在按钮附近迅速呈现一个层来做这些事呢,类似快捷菜单那样,用户高兴就在里面做一下该做的事,不高兴就在其它地方点一下它就消失,本来很轻便快捷的操作,DUANG~弹出一个窗体来会不会令用户心里咯噔一下呢,感受层面的事

  • C# winform实现右下角弹出窗口结果的方法

    本文实例讲述了C# winform实现右下角弹出窗口结果的方法.分享给大家供大家参考,具体如下: using System.Runtime.InteropServices; [DllImport("user32")] private static extern bool AnimateWindow(IntPtr hwnd, int dwTime, int dwFlags); //下面是可用的常量,按照不合的动画结果声明本身须要的 private const int AW_HOR_POS

  • vue2.0的contextmenu右键弹出菜单的实例代码

    整理文档,搜刮出一个vue2.0的contextmenu右键弹出菜单的实例代码,稍微整理精简一下做下分享. 1.事情对象 <!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <script src="http://unpkg.com/vue/dist/vue.js"></script>

  • jquery原创弹出层折叠效果点击折叠弹出一个层

    弹出层效果很多网站上都用到,今天就整理最近项目里用到的一个小效果,点击折叠弹出一个层给用户填写信息.弹出层代码都是jq动态创建,每个人写法都不一样,需求也不一样,所有选择符合自已的即可. html: 复制代码 代码如下: <h1 class="bm"><a href="javascript:;">我要报名</a></h1> 复制代码 代码如下: *{ margin:0; padding:0;} body{ font:1

  • JavaScript实现的弹出遮罩层特效经典示例【基于jQuery】

    本文实例讲述了JavaScript实现的弹出遮罩层特效.分享给大家供大家参考,具体如下: 这篇给大家分享一个简单的遮罩层特效,先上效果图. 代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>查看,修改,删除</title> <script src="http://libs.baidu

  • jQuery实现打开网页自动弹出遮罩层或点击弹出遮罩层功能示例

    本文实例讲述了jQuery实现打开网页自动弹出遮罩层或点击弹出遮罩层功能.分享给大家供大家参考,具体如下: 弹出层:两种方式 一是打开网页就自动弹出层 二是点击弹出 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="htt

  • jQuery弹出遮罩层效果完整示例

    本文实例讲述了jQuery弹出遮罩层效果.分享给大家供大家参考,具体如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head&

  • jQuery点击按钮弹出遮罩层且内容居中特效

    本文为大家分享了jQuery点击按钮弹出遮罩层且内容居中的特效,下面来看最终实现的效果: 由于是测试的程序,所以我未加关闭的按钮. 一.主体程序 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>弹出居中遮罩</title> <meta name="viewport" content="width=devi

  • 利用js对象弹出一个层

    复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml">  <head>  <meta http-equiv=

  • jQuery实现弹出窗口弹出div层的实例代码

    通过今天的jquery实例学习,我们要达到这样的效果:点击页面的链接,弹出一个div层,同时页面的其他部分变灰并且不能点击:无论是改变浏览器窗口大小还是下拉滚动条,这个弹出层都能始终保持居中:点击页面的关闭按钮,弹出层消失,页面恢复原样. 这里借鉴之前的一篇文章<基于jQuery的固定飘浮层>,使弹出窗口可以始终固定在浏览器的正中间.在这里有一个要点,就是如何使页面的其他地方在弹出窗口的同时变灰.我使用的方法就是在点击链接弹出div层的时候,给页面增加一个div层,这个层就"负责&q

随机推荐