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

------------------201508261813更新(源码有更新、Demo未更新)------------------

  • 重新绘制调整大小手柄(SizeGrip,右下角那个),因为系统自绘的太靠边角,在XP下会盖过那部分边框,视觉体验不好。改进如图:

    新增的DrawSizeGrip方法就是绘制方法,是protected virtual的,所以如果你看不上我画的这个,可以在子类重写该方法画你自己满意的(题外,画这个我还参考了VS2010的效果,不过是相反的,VS的是凸起效果,我这是塌陷style)
  • 支持四边+四角全方位拖动改变浮动层尺寸,改善体验。如图:

注:浮动层是否可以调整大小是根据SizeGripStyle属性决定,分3种情况:

  • SizeGripStyle为Show,则始终允许用户调整大小,手柄会出现、鼠标移至边缘边角会产生视觉变化并可以拖动
  • SizeGripStyle为Hide,则始终禁止用户调整大小,手柄不会出现、鼠标移至边缘边角不会产生视觉变化,也不可以拖动改变大小
  • SizeGripStyle为Auto,则在模式化打开(Modal为true,即通过ShowDialog打开的)时与Show一致,非模式化打开(Modal为false,通过Show打开)时与Hide一致,这也是原版Form的逻辑,只不过原版Form还会根据FormBorderStyle,但本类已将该属性固化,所以请注意Auto这货,建议始终显式指定Show/Hide为妙

------------------201508251458更新------------------

  • 激活首控件之前是在OnShown中进行,经过研究,改为令TopMost=true,就能使浮动层与正常窗体有一致的激活首控件行为,同时省却了对OnShown的重写
  • 解决子控件有时没有聚焦框(焦点虚线框)的问题。如图:

注:最后的demo没更新,请重新取FloatLayerBase.cs源码就好

------------------201508240846原文(已更新)------------------

背景:

有时候我们需要开一个简单的窗口来做一些事,例如输入一些东西、点选一个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,就可以实现自己的浮动应用。

使用说明:

  1. 确保FloatLayerBase类在项目中~废话。源码在此:

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms; namespace AhDung.WinForm.Controls
    {
    /// <summary>
    /// 浮动层基类
    /// </summary>
    //Update:201508251451
    //- 将由OnShow中负责的首控件激活改为设TopMost=true实现,同时移除OnShow重写
    //- 解决子控件无聚焦框(焦点虚线框,FocusCues)的问题
    //Update:201508261806
    //- 重绘右下角调整大小手柄,解决系统自绘在XP下太靠边角从而覆盖边框的问题
    //- 支持边缘和边角拖动改变窗体大小
    //- 启用双缓冲
    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;
    int _borderWidth;//边框宽度,用于绘制SizeGrip时计算边角偏移 /// <summary>
    /// 获取所绘制的边框尺寸(边框宽度x2)
    /// </summary>
    [Browsable(false)]
    public Size BorderSize
    {
    get { return new Size(_borderWidth, _borderWidth); }
    } /// <summary>
    /// 指示窗体是否处于可调整大小状态
    /// </summary>
    [Browsable(false)]
    public bool CanReSize
    {
    get
    {
    return this.SizeGripStyle == System.Windows.Forms.SizeGripStyle.Show
    || (this.SizeGripStyle == System.Windows.Forms.SizeGripStyle.Auto && Modal);
    }
    } /// <summary>
    /// 获取或设置边框类型
    /// </summary>
    [Description("获取或设置边框类型。")]
    [DefaultValue(BorderStyle.Fixed3D)]
    public BorderStyle BorderType
    {
    get { return _borderType; }
    set
    {
    if (_borderType == value) { return; }
    _borderType = value;
    this.UpdateBorderWidth();
    Invalidate();
    }
    } /// <summary>
    /// 获取或设置三维边框样式
    /// </summary>
    [Description("获取或设置三维边框样式。")]
    [DefaultValue(Border3DStyle.RaisedInner)]
    public Border3DStyle Border3DStyle
    {
    get { return _border3DStyle; }
    set
    {
    if (_border3DStyle == value) { return; }
    _border3DStyle = value;
    this.UpdateBorderWidth();
    Invalidate();
    }
    } /// <summary>
    /// 获取或设置线型边框样式
    /// </summary>
    [Description("获取或设置线型边框样式。")]
    [DefaultValue(ButtonBorderStyle.Solid)]
    public ButtonBorderStyle BorderSingleStyle
    {
    get { return _borderSingleStyle; }
    set
    {
    if (_borderSingleStyle == value) { return; }
    _borderSingleStyle = value;
    this.UpdateBorderWidth();
    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 = ;
    //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); this.DoubleBuffered = true; //初始化基类属性
    InitBaseProperties(); //初始化边框相关
    _borderType = BorderStyle.Fixed3D;
    _border3DStyle = System.Windows.Forms.Border3DStyle.RaisedInner;
    _borderSingleStyle = ButtonBorderStyle.Solid;
    _borderColor = Color.DarkGray;
    this.UpdateBorderWidth();
    } protected override void OnLoad(EventArgs e)
    {
    //防止重入
    if (_isShowDialogAgain) { return; } //为首次ShowDialog设标记
    if (Modal) { _isShowDialogAgain = true; } //需得减掉两层边框宽度,运行时尺寸才与设计时完全相符,原因不明
    //确定与ControlBox、FormBorderStyle有关,但具体联系不明
    if (!DesignMode)
    {
    Size size = SystemInformation.FrameBorderSize;
    this.Size -= size + size;//不可以用ClientSize,后者会根据窗口风格重新调整Size
    }
    base.OnLoad(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);
    }
    }
    else if (m.Msg == 0x84 && this.CanReSize)//WM_NCHITTEST。实现边缘和边角拖动改变窗体大小
    {
    Point pt = this.PointToClient(NativeMethods.MakePoint(m.LParam));
    Size size = this.ClientSize;
    if (new Rectangle(, , , ).Contains(pt))
    {
    m.Result = (IntPtr);//HTTOPLEFT
    return;
    }
    if (new Rectangle(, , size.Width - , ).Contains(pt))
    {
    m.Result = (IntPtr);//HTTOP
    return;
    }
    if (new Rectangle(size.Width - , , , ).Contains(pt))
    {
    m.Result = (IntPtr);//HTTOPRIGHT
    return;
    }
    if (new Rectangle(size.Width - , , , size.Height - - ).Contains(pt))
    {
    m.Result = (IntPtr);//HTRIGHT
    return;
    }
    if (new Rectangle(, size.Height - , size.Width - - , ).Contains(pt))
    {
    m.Result = (IntPtr);//HTBOTTOM
    return;
    }
    if (new Rectangle(, size.Height - , , ).Contains(pt))
    {
    m.Result = (IntPtr);//HTBOTTOMLEFT
    return;
    }
    if (new Rectangle(, , , size.Height - ).Contains(pt))
    {
    m.Result = (IntPtr);//HTLEFT
    return;
    }
    }
    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);
    }
    } protected override void OnPaint(PaintEventArgs e)
    {
    if (this.CanReSize)
    {
    Size clientSize = this.ClientSize;
    Rectangle rect = new Rectangle(clientSize.Width - , clientSize.Height - , , ); //画手柄
    DrawSizeGrip(e.Graphics, new Rectangle(rect.Location - BorderSize - new Size(, ), rect.Size)); //刨掉SizeGrip区域,防止基类再画
    e.Graphics.SetClip(rect, System.Drawing.Drawing2D.CombineMode.Exclude);
    }
    base.OnPaint(e);
    e.Graphics.ResetClip();
    } /// <summary>
    /// 绘制SizeGrip(调整大小的手柄),子类可重写
    /// </summary>
    /// <param name="g">绘制器</param>
    /// <param name="rect">建议作图区域</param>
    protected virtual void DrawSizeGrip(Graphics g, Rectangle rect)
    {
    Color backColor = this.BackColor;
    Brush color1 = new SolidBrush(ControlPaint.Dark(backColor));
    Brush color2 = new SolidBrush(ControlPaint.Dark(backColor, -0.5F));
    Brush color3 = new SolidBrush(ControlPaint.Dark(backColor, -0.1F));
    Brush color4 = new SolidBrush(ControlPaint.Light(backColor));
    Point pt = new Point(rect.X + , rect.Y + );//左上角偏移 for (int i = ; i < ; i++)
    {
    for (int j = ; j < ; j++)
    {
    if (j >= - i)
    {
    g.FillRectangle(color1, new Rectangle(pt.X + j * , pt.Y + i * , , ));
    g.FillRectangle(color2, new Rectangle(pt.X + j * + , pt.Y + i * , , ));
    g.FillRectangle(color3, new Rectangle(pt.X + j * , pt.Y + i * + , , ));
    g.FillRectangle(color4, new Rectangle(pt.X + j * + , pt.Y + i * + , , ));
    }
    }
    }
    } protected override void OnVisibleChanged(EventArgs e)
    {
    if (!DesignMode)
    {
    if (Visible)
    {
    //使焦点子控件拥有聚焦框,重写ShowFocusCues较麻烦
    NativeMethods.SendMessage(this.Handle, 0x127/*WM_CHANGEUISTATE*/, (IntPtr)0x10002/*UISF_HIDEFOCUS | UIS_CLEAR*/, IntPtr.Zero);
    NativeMethods.SendMessage(this.Handle, 0x128/*WM_UPDATEUISTATE*/, (IntPtr)0x10002/*UISF_HIDEFOCUS | UIS_CLEAR*/, IntPtr.Zero); //显示后添加鼠标消息筛选器以开始捕捉
    Application.AddMessageFilter(_mouseMsgFilter);
    }
    else
    {
    //隐藏时则移除筛选器。之所以不放Dispose中是想尽早移除筛选器
    Application.RemoveMessageFilter(_mouseMsgFilter);
    }
    }
    base.OnVisibleChanged(e);
    } //实现窗体客户区拖动
    //在WndProc中实现这个较麻烦,所以放到这里做
    protected override void OnMouseDown(MouseEventArgs e)
    {
    //让鼠标点击客户区时达到与点击标题栏一样的效果,以此实现客户区拖动
    NativeMethods.ReleaseCapture();
    NativeMethods.SendMessage(Handle, 0xA1/*WM_NCLBUTTONDOWN*/, (IntPtr)/*CAPTION*/, IntPtr.Zero); base.OnMouseDown(e);
    } /// <summary>
    /// 显示为模式窗体
    /// </summary>
    /// <param name="control">显示在该控件下方</param>
    public DialogResult ShowDialog(Control control)
    {
    return ShowDialog(control, , 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, , 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, , 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, , 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;
    } /// <summary>
    /// 更新边框宽度
    /// </summary>
    private void UpdateBorderWidth()
    {
    if (_borderType == BorderStyle.None)
    {
    _borderWidth = ;
    }
    else if (_borderType == BorderStyle.Fixed3D)
    {
    if (_border3DStyle == System.Windows.Forms.Border3DStyle.Adjust) { _borderWidth = ; }
    else if (_border3DStyle == System.Windows.Forms.Border3DStyle.Flat) { _borderWidth = ; }
    else { _borderWidth = CountOneInBits((uint)_border3DStyle); }
    }
    else
    {
    if (_borderSingleStyle == ButtonBorderStyle.None) { _borderWidth = ; }
    else if (_borderSingleStyle == ButtonBorderStyle.Outset) { _borderWidth = ; }
    else { _borderWidth = ; }
    }
    } /// <summary>
    /// 统计二进制中1的个数
    /// </summary>
    private static int CountOneInBits(uint num)
    {
    int count = ;
    while (num != )
    {
    num &= num - ;
    count++;
    }
    return count;
    } #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 = true; //使本窗体像普通窗体一样显示后自动激活首控件
    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 true; } 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;
    } public static int LOWORD(IntPtr n)
    {
    return ((int)n) & 0xFFFF;
    } public static int HIWORD(IntPtr n)
    {
    return (((int)n) >> ) & 0xFFFF;
    } public static Point MakePoint(IntPtr n)
    {
    return new Point(LOWORD(n), HIWORD(n));
    }
    }
    }
    }

    FloatLayerBase.cs

  2. 新建继承窗体,选择继承自FloatLayerBase类;也可以新建普通窗体,然后把基类由Form改为FloatLayerBase
  3. 在设计器和源码中打造浮动应用
  4. 在需要的地方使用它。关于使用,先看一下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供您体验。

-文毕-

【C#】分享一个弹出容器层,像右键菜单那样召即来挥则去的更多相关文章

  1. [转]【C#】分享一个弹出浮动层,像右键菜单那样召即来挥则去

    适用于:.net2.0+ Winform项目 背景: 有时候我们需要开一个简单的窗口来做一些事,例如输入一些东西.点选一个item之类的,可能像这样: 完了返回原窗体并获取刚刚的输入,这样做并没有什么 ...

  2. Css动画形式弹出遮罩层,内容区上下左右居中于不定宽高的容器中

    <!DOCTYPE html> <html> <head> </head> <body id="body"> <! ...

  3. 利用React/anu编写一个弹出层

    本文将一步步介绍如何使用React或anu创建 一个弹出层. React时代,代码都是要经过编译的,我们很多时间都耗在babel与webpack上.因此本文也介绍如何玩webpack与babel. 我 ...

  4. 点击图片或者鼠标放上hover .图片变大. 1)可以使用css中的transition, transform 2) 预先设置一个 弹出div. 3)弹出层 alert ; 4) 浏览器的宽度document.documentElement.clientWidth || document.body.clientWidth

    变大: 方法一: 利用css属性. 鼠标放上 hover放大几倍. .kecheng_02_cell_content img { /*width: 100px; height: 133px;*/ wi ...

  5. 移动端和PC端弹出遮罩层后,页面禁止滚动的解决方法及探究

    PC端解决方案 pc端的解决思路就是在弹出遮罩层的时候取消已经存在的滚动条,达到无法滚动的效果. 也就是说给body添加overflow:hidden属性即可,IE6.7下不会生效,需要给html增加 ...

  6. (转载)Android项目实战(十七):QQ空间实现(二)—— 分享功能 / 弹出PopupWindow

    Android项目实战(十七):QQ空间实现(二)—— 分享功能 / 弹出PopupWindow   这是一张QQ空间说说详情的截图. 分析: 1.点击右上角三个点的图标,在界面底部弹出一个区域,这个 ...

  7. 创建一个弹出DIV窗口

    创建一个弹出DIV窗口 摘自:   http://www.cnblogs.com/TivonStone/archive/2012/03/20/2407919.html 创建一个弹出DIV窗口可能是现在 ...

  8. jquery特效(7)—弹出遮罩层且内容居中

    上周写了几个小特效,其中有个点击按钮弹出遮罩层的特效,下面来看最终实现的效果: 由于是测试的程序,所以我未加关闭的按钮. 一.主体程序 <!DOCTYPE html> <html&g ...

  9. 【JS新手教程】弹出两层div,及在LODOP内嵌上层

    前面的博文有个简单的弹出div层[JS新手教程]浏览器弹出div层1,有一层,不过为了提示,一般会不让用户可以点击该提示之外的地方的.如果让用户弹出层后,把其他的按钮和链接都设置不可用应该比较麻烦,如 ...

随机推荐

  1. jQuery.Callbacks之demo

    jQuery.Callbacks是jquery在1.7版本之后加入的,是从1.6版中的_Deferred对象中抽离的,主要用来进行函数队列的add.remove.fire.lock等操作,并提供onc ...

  2. java中文乱码解决之道(三)-----编码详情:伟大的创想---Unicode编码

    随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...

  3. 浅谈算法和数据结构: 十 平衡查找树之B树

    前面讲解了平衡查找树中的2-3树以及其实现红黑树.2-3树种,一个节点最多有2个key,而红黑树则使用染色的方式来标识这两个key. 维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种 ...

  4. 用java PreparedStatement就不用担心sql注入了吗?

    先感慨下,好久没写博客了,一是工作太忙,二是身体不太给力,好在终于查清病因了,趁着今天闲下来,迫不及待与读者交流,最后忠告一句:身体是活着的本钱! 言归正传,对java有了解的同学基本上都体验过JDB ...

  5. [New Portal]Windows Azure Storage (14) 使用Azure Blob的PutBlock方法,实现文件的分块、离线上传

    <Windows Azure Platform 系列文章目录> 相关内容 Windows Azure Platform (二十二) Windows Azure Storage Servic ...

  6. .NET 基础 一步步 一幕幕 [.NET 系列预热]

    .NET 系列预热 .NET : 一般指.Net Framework框架.一种平台,一种技术. .NET 下的编程语言有C#,F#,VB:在这里我们主要讲述的C#. .NET 一般都能干什么呢? l  ...

  7. java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查

    java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查 一.问题:学习HashMap时候,我做了这样一个程序: impor ...

  8. ls /usr/linkapp 没反应

    ls /usr/linkapp ll /usr/linkapp  都是一样无反应 没有任何反应, ctrl + c /  ctrl + d 都不行 但是 ls /usr/linkapp/ | wc - ...

  9. Android 透明度百分比对应的 十六进制

    Android 透明度百分比对应的 十六进制 先把结果放在这里,方便大家查询,也方便自己,UI太喜欢用百分比表示了=.=! 透明度百分比对应的十六进制: (说明:百分比计算出来会有小数,按照常规的四舍 ...

  10. iOS-SDWebimage底层实现原理

    其实有些框架的实现原理,并没有想象中那么难,思想也很简单,主要是更新第三方框架的作者对自己写的代码,进行了多层封装,使代码的可读性降低,也就使得框架看起来比较难.我来实现以下SDWebimage的的曾 ...