转(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);
}
下一篇: Pytorch优化器-Optimizer