欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

转(C# 类似右键菜单弹出窗体)

程序员文章站 2022-07-06 10:19:33
文章来自 https://www.cnblogs.com/ahdung/p/FloatLayerBase.html 每天进步一点点 新建类 FloatLayerBase 继承Form, 自己有点小改动public void Show(Control control, Point endPoint) ......

文章来自 https://www.cnblogs.com/ahdung/p/floatlayerbase.html

每天进步一点点

新建类  floatlayerbase 继承form,

自己有点小改动public void show(control control, point endpoint) 添加参数 endpoint 避免窗体在最右边或下边时弹出窗体被遮掩。

 

public partial 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);
            //initializecomponent();
            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, point endpoint)
        {
            return showdialog(control, 0, control.height, endpoint);
        }

        /// <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, point endpoint)
        {
            return showdialog(control, new point(offsetx, offsety), endpoint);
        }

        /// <summary>
        /// 显示为模式窗体
        /// </summary>
        /// <param name="control">触发弹出窗体的控件</param>
        /// <param name="offset">相对control偏移</param>
        public dialogresult showdialog(control control, point offset, point endpoint)
        {
            return this.showdialoginternal(control, offset, endpoint);
        }

        /// <summary>
        /// 显示为模式窗体
        /// </summary>
        /// <param name="item">显示在该工具栏项的下方</param>
        public dialogresult showdialog(toolstripitem item, point endpoint)
        {
            return showdialog(item, 0, item.height, endpoint);
        }

        /// <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, point endpoint)
        {
            return showdialog(item, new point(offsetx, offsety), endpoint);
        }

        /// <summary>
        /// 显示为模式窗体
        /// </summary>
        /// <param name="item">触发弹出窗体的工具栏项</param>
        /// <param name="offset">相对item偏移</param>
        public dialogresult showdialog(toolstripitem item, point offset, point endpoint)
        {
            return this.showdialoginternal(item, offset, endpoint);
        }

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

        /// <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, point endpoint)
        {
            show(control, new point(offsetx, offsety), endpoint);
        }

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

        /// <summary>
        /// 显示窗体
        /// </summary>
        /// <param name="item">显示在该工具栏下方</param>
        public void show(toolstripitem item, point endpoint)
        {
            show(item, 0, item.height, endpoint);
        }

        /// <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, point endpoint)
        {
            show(item, new point(offsetx, offsety),endpoint);
        }

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

        /// <summary>
        /// showdialog内部方法
        /// </summary>
        private dialogresult showdialoginternal(component controloritem, point offset, point endpoint)
        {
            //快速连续弹出本窗体将有可能遇到尚未hide的情况下再次弹出,这会引发异常,故需做处理
            if (this.visible) { return system.windows.forms.dialogresult.none; }

            this.setlocationandowner(controloritem, offset, endpoint);
            return base.showdialog();
        }

        /// <summary>
        /// show内部方法
        /// </summary>
        private void showinternal(component controloritem, point offset, point endpoint)
        {
            if (this.visible) { return; }//原因见showdialoginternal

            this.setlocationandowner(controloritem, offset, endpoint);
            base.show();
        }

        /// <summary>
        /// 设置坐标及所有者
        /// </summary>
        /// <param name="controloritem">控件或工具栏项</param>
        /// <param name="offset">相对偏移</param>
        private void setlocationandowner(component controloritem, point offset, point endpoint)
        {
            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);
            if (pt.x + this.restorebounds.width > endpoint.x)//右边超出界面
            {
                pt.x = pt.x + c.width - this.restorebounds.width;
            }

            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;
            //control c1 = c.parent;
            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;
            }

            //[dllimport("user32.dll", exactspelling = true)]
            //public static extern intptr getancestor(intptr hwnd, uint flags);
        }
    }

  

1  添加用户控件usercontrol1继承floatlayerbase

 

2 调用代码

 public floatlayerbase createlayer(type type, out bool isshow)
        {
            //考虑到初始化体验爽滑,不用反射
            floatlayerbase layer = null;
            if (type == typeof(usercontrol1)) { layer = new usercontrol1(); } }

            layerformoption opts = layeroption;
            isshow = opts.isshow;
            layer.bordertype = opts.bordertype;
            layer.border3dstyle = opts.border3dstyle;
            layer.bordersinglestyle = opts.bordersinglestyle;
            layer.bordercolor = opts.bordercolor;
            return layer;
        }

        public void popuplayer(type type, object sender)
        {
            control c = sender as control;
            toolstripitem item = sender as toolstripitem;
            point pt = new point(this.clientrectangle.width, this.clientrectangle.height);

            bool isshow;
            floatlayerbase p = createlayer(type, out isshow);
            if (isshow)
            {
                if (c != null) { p.show(c, pt); }
                else { p.show(item, pt); }
            }
            else
            {
                var result = c != null ? p.showdialog(c, pt) : p.showdialog(item, pt);
                //txbresult.appendtext(result + "\r\n");
            }
        }

private void button1_click(object sender, eventargs e)
{
    popuplayer(typeof(usercontrol1), sender);
}