如何实现一个无边框Form的移动和改变大小(二)
接着上文:这里写链接内容
我们来说说一个比较复杂的实现,
效果如图:
注意为了能够凸显没有NC(NotClient)区域,我们额外用了3个panel分别放在窗体的左右和下部。用来模拟客户自己的控件。
下面我们说下这种真正的无边框Form的实现方法
下面先无责任的贴下代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using norlib;
using norlib.Controls;
using norlib.Error;
using norlib.Native;
using norlib.SystemExtension;
namespace norlib.Controls
{
public partial class BorderlessForm
:Form
{
public BorderlessForm()
{
InitializeComponent();
_caption = 0;
_mp = new MousePreview(this);
#region 初始化_dtCursor
_dtCursor.Add(HT.HTBOTTOM, Cursors.SizeNS);
_dtCursor.Add(HT.HTTOP, Cursors.SizeNS);
_dtCursor.Add(HT.HTLEFT, Cursors.SizeWE);
_dtCursor.Add(HT.HTRIGHT, Cursors.SizeWE);
_dtCursor.Add(HT.HTTOPLEFT, Cursors.SizeNWSE);
_dtCursor.Add(HT.HTTOPRIGHT, Cursors.SizeNESW);
_dtCursor.Add(HT.HTBOTTOMLEFT, Cursors.SizeNESW);
_dtCursor.Add(HT.HTBOTTOMRIGHT, Cursors.SizeNWSE);
#endregion
_mp.AddMouseMessage(WM.WM_LBUTTONDOWN, mp_LButtonDownPreview);
_mp.AddMouseMessage(WM.WM_MOUSEMOVE, mp_MouseMovePreview);
//不能选这个
//this.Capture = true;
}
public int Border
{
get { return _border; }
set
{
if (value <= 0)
return;
_border = value;
}
}
public int Caption
{
get { return _caption; }
set { _caption = value; }
}
eMPResult mp_LButtonDownPreview(WM arg_wm, ref MOUSEHOOKSTRUCT argr_stInfo)
{
var p = argr_stInfo.pt;
var cp = this.PointToClient(new Point(p.x, p.y));
var hc = _GetHT(cp, _border, _caption);
if (hc != HT.HTERROR && hc != HT.HTCLIENT)
{
Task.Factory.StartNew(() =>
{
this.BeginInvoke(new Action(()=>
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(this.Handle, WM.WM_NCLBUTTONDOWN, (UIntPtr)hc, (IntPtr)0);
}));
});
return eMPResult.CutOffMessage;
}
else
{
return eMPResult.ContinueHook;
}
}
eMPResult mp_MouseMovePreview(WM arg_wm, ref MOUSEHOOKSTRUCT argr_stInfo)
{
var p = argr_stInfo.pt;
var cp = this.PointToClient(new Point(p.x, p.y));
var hc = _GetHT(cp, _border, _caption);
if (hc != HT.HTCLIENT && hc != HT.HTERROR)
{
var c = _GetCursor(hc);
if (Cursor != c)
Cursor = c;
return eMPResult.ContinueHook;
}
else
{
Cursor = Cursors.Default;
return eMPResult.ContinueHook;
}
}
HT _GetHT(Point arg_p, int arg_border, int arg_caption)
{
var pos = arg_p;
var border = arg_border;
var caption = arg_caption;
if (pos.X < 0 || pos.Y < 0)
{
//
//非法位置
//
return HT.HTERROR;
}
else if (pos.X <= border)
{
//
//左侧
//
if (pos.Y <= border)
{
//左上侧
return HT.HTTOPLEFT;
}
else if (pos.Y >= this.Height - border)
{
//左下侧
return HT.HTBOTTOMLEFT;
}
else
{
//左侧
return HT.HTLEFT;
}
}
else if (pos.X >= this.Width - border)
{
//
//右侧
//
if (pos.Y <= border)
{
//右上侧
return HT.HTTOPRIGHT;
}
else if (pos.Y >= this.Height - border)
{
//右下侧
return HT.HTBOTTOMRIGHT;
}
else
{
//右侧
return HT.HTRIGHT;
}
}
else
{
//
//中部
//
if (pos.Y <= border)
{
//上中侧
return HT.HTTOP;
}
else if (pos.Y >= this.Height - border)
{
//下中侧
return HT.HTBOTTOM;
}
else if (pos.Y <= caption)
{
return HT.HTCAPTION;
}
else
{
return HT.HTCLIENT;
}
}
}
Cursor _GetCursor(HT arg_ht)
{
var cursor = (Cursor)null;
if (_dtCursor.TryGetValue(arg_ht, out cursor))
{
return cursor;
}
else
{
return Cursors.Default;
}
}
MousePreview _mp;
readonly Dictionary<HT, Cursor> _dtCursor = new Dictionary<HT, Cursor>();
int _border = 5;
int _caption = 20;
}
}
其主要思想是通过设置SetWindowsHookEx的WH_MOUSE来截获当前应用程序的鼠标事件。
随后我们对WM_LBUTTONDOWN的消息加点料
var p = argr_stInfo.pt;
var cp = this.PointToClient(new Point(p.x, p.y));
var hc = _GetHT(cp, _border, _caption);
if (hc != HT.HTERROR && hc != HT.HTCLIENT)
{
Task.Factory.StartNew(() =>
{
this.BeginInvoke(new Action(()=>
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(this.Handle, WM.WM_NCLBUTTONDOWN, (UIntPtr)hc, (IntPtr)0);
}));
});
return eMPResult.CutOffMessage;
}
else
{
return eMPResult.ContinueHook;
}
_GetHT函数用于计算客户点(cp)是属于哪一个区域,包括但不限于HT_CAPTION,HT_CLIENT,HT_HTLEFT.
此函数主要通过给定的边框宽度border和给定的标题栏高度caption来确定NC(Not Client)区域的范围。(这里说一句题外话,如果你的border和caption设置的过大, 导致你的控件位于NC区域的部分将无法响应鼠标信息-_-#)接着说下去,点击区域位于我们认为的NC区域,我们截断此消息(Cut off message),否则放过此消息。截获消息的同时,异步发送WM_NCLBUTTONDOWN消息给窗体的消息处理函数,要求窗体处理NC消息
- 这样初步完成了Broderless的效果。为了进一步完善鼠标显示的效果,可以截获WM.WM_MOUSEMOVE消息,随后显示对应的光标
说一下实际使用中,强烈不推荐使用Caption这个属性,建议这个属性设置为0,然后自己实现一个Caption的控件,捕获MouseDown,然后自己发送NC消息。因为如果你使用Caption的话,你都没法在Caption上弄个关闭按钮,所以我其实是这么搞得:
public partial class FormBorderless2 : BorderlessForm
{
public FormBorderless2()
{
InitializeComponent();
Border = 0;
panelCaption.MouseDown += panelCaption_MouseDown;
btnClose.Click += btnClose_Click;
}
void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
void panelCaption_MouseDown(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(this.Handle, WM.WM_NCLBUTTONDOWN, (UIntPtr)HT.HTCAPTION, (IntPtr)0);
}
}
}
我另外搞了一个panelCaption,来实现各种按钮以及Caption的效果。
最后贴一下MousePreivew的实现
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using NM = norlib.Native.NativeMethods;
using norlib.Native;
using norlib.SystemExtension;
namespace norlib.Controls
{
/// <summary>
/// 精简的 NM.HOOKMOUSEPROC
/// 1.去掉了HC nCode
/// 因为只响应HC.HC_ACTION
/// 2.将UIntPtr wParam直接转化为WM
/// 方便用户使用
/// 3.返回值的作用不同于NM.HOOKMOUSEPROC
/// </summary>
/// <param name="arg_wm"></param>
/// <param name="stHookStruct"></param>
/// <returns></returns>
public delegate eMPResult MPMOUSEPROC(WM wm, ref MOUSEHOOKSTRUCT stHookStruct);
public class MousePreview
{
/// <summary>
/// 把相关的鼠标消息发送给此(控件/窗体)
/// </summary>
/// <param name="arg_parent">
/// </param>
public MousePreview(Control arg_parent)
{
if (null == arg_parent)
throw new NotSupportedException(string.Format("{0}的构造函数不接受参数arg_parent为Null", typeof(MousePreview).Name));
_hookProc = new NM.HOOKMOUSEPROC(_MyMouseProc);
_hookHandler = NM.SetWindowsHookEx(WH.WH_MOUSE, _hookProc, 0, NM.GetCurrentThreadId());
_parent = arg_parent;
}
~MousePreview()
{
ReleasePreview();
}
/// <summary>
/// 将截获的消息改造,计算为适合此(控件/窗体)的正确格式后,
/// 发送给此(控件/窗体)
/// </summary>
/// <param name="msg"></param>
/// <param name="arg_mpc">
/// 截获子(控件/窗体)或者仅截获顶层窗体的消息
/// </param>
/// <returns></returns>
public bool AddMouseMessage(WM msg, eMPCategory arg_mpc = eMPCategory.Sub)
{
try
{
if (null == _parent)
return false;
_dtCategory.Add(msg, arg_mpc);
_dtWM.Add(msg, _DefaultMPMouseProc);
return true;
}
catch (System.Exception ex)
{
return false;
}
}
public bool AddMouseMessage(WM msg, MPMOUSEPROC mouseProc, eMPCategory arg_mpc = eMPCategory.Sub)
{
try
{
_dtCategory.Add(msg, arg_mpc);
_dtWM.Add(msg, mouseProc);
return true;
}
catch (System.Exception ex)
{
return false;
}
}
public void ReleasePreview()
{
if(_hookHandler != IntPtr.Zero)
{
NM.UnhookWindowsHookEx(_hookHandler);
_hookHandler = IntPtr.Zero;
}
}
/// <summary>
///
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam">
/// 将要被传递的消息Id
/// </param>
/// <param name="stHookStruct"></param>
/// <returns></returns>
IntPtr _MyMouseProc(HC nCode, UIntPtr wParam, ref MOUSEHOOKSTRUCT stHookStruct)
{
if(nCode != HC.HC_ACTION)
return NM.CallNextHookEx(_hookHandler, nCode, wParam, ref stHookStruct);
var msg = (WM)wParam;
var fn = (MPMOUSEPROC)null;
if(!_dtWM.TryGetValue(msg, out fn))
return NM.CallNextHookEx(_hookHandler, nCode, wParam, ref stHookStruct);
var e = _dtCategory[msg];
if(_parent != null && !_Belong(_parent, ref stHookStruct, e))
return NM.CallNextHookEx(_hookHandler, nCode, wParam, ref stHookStruct);
var r = fn(msg, ref stHookStruct);
if (eMPResult.ContinueHook == r)
return NM.CallNextHookEx(_hookHandler, nCode, wParam, ref stHookStruct);
else if (eMPResult.CutOffMessage == r)
{
return (IntPtr)(int)-1;
}
else if (eMPResult.CutOffNextHook == r)
{
return (IntPtr)0;
}
else
{
throw new NotImplementedException();
}
}
bool _Belong(Control arg_control, ref MOUSEHOOKSTRUCT stHookStruct, eMPCategory arg_e)
{
var b = false;
if(arg_e.HasFlag(eMPCategory.Sub))
{
b = b | ((IntPtr)stHookStruct.hWnd).Belong(arg_control, arg_e.HasFlag(eMPCategory.Myself)/*false*/);
}
if (arg_e.HasFlag(eMPCategory.InRangeNoFocus))
{
var form = arg_control.GetRootForm();
if (!arg_control.IsDisposed &&
stHookStruct.hWnd == arg_control.Handle &&
(form.GetFocusedControl()==null) &&
stHookStruct.pt.ToPoint().IsIn(arg_control, true))
{
b = b | true;
}
else
{
b = b | false;
}
}
return b;
}
eMPResult _DefaultMPMouseProc(WM msg, ref MOUSEHOOKSTRUCT stHookStruct)
{
var wParam = (UIntPtr)_GetKeyStates();
var st = stHookStruct;
_parent.BeginInvoke(new Action(() =>
{
var p = _parent.PointToClient(new Point(st.pt.x, st.pt.y));
var lParam = (IntPtr)(p.X + (p.Y << 16));
Native.NativeMethods.SendMessage(_parent.Handle, msg, wParam, lParam);
}));
return eMPResult.ContinueHook;
}
int _GetKeyStates()
{
int retval = 0;
if (NM.HIWORD(NM.GetKeyState(VK.VK_LBUTTON))> 0)
retval += 1;
if (NM.HIWORD(NM.GetKeyState(VK.VK_RBUTTON)) > 0)
retval += 2;
if (NM.HIWORD(NM.GetKeyState(VK.VK_SHIFT)) > 0)
retval += 4;
if (NM.HIWORD(NM.GetKeyState(VK.VK_CONTROL)) > 0)
retval += 8;
if (NM.HIWORD(NM.GetKeyState(VK.VK_MBUTTON)) > 0)
retval += 16;
if (NM.HIWORD(NM.GetKeyState(VK.VK_XBUTTON1)) > 0)
retval += 32;
if (NM.HIWORD(NM.GetKeyState(VK.VK_XBUTTON2)) > 0)
retval += 64;
return retval;
}
/// <summary>
/// 把此(控件/窗体)的子控件消息传递给此父窗体
/// </summary>
Control _parent;
NM.HOOKMOUSEPROC _hookProc;
IntPtr _hookHandler;
readonly Dictionary<WM, MPMOUSEPROC> _dtWM = new Dictionary<WM, MPMOUSEPROC>();
readonly Dictionary<WM, eMPCategory> _dtCategory = new Dictionary<WM, eMPCategory>();
}
public enum eMPCategory
:int
{
/// <summary>
/// 所有子(控件/窗体)的消息发送给目标(控件/窗体)
/// </summary>
Sub = 1,
/// <summary>
/// 消息来源是没有Focus的Form下的目标(控件/窗体)的子控件
/// 例如一个没有焦点的Form被客户点击了此Form中的子控件
/// 一般用于捕获窗体的自定义Caption区域( 没有焦点的Form的子控件第一次被单击时,子控件没有OnMouseDown消息)
/// InRange表示鼠标点击在目标(控件/窗体)
/// NoFocus表示目标(控件/窗体)所在的Form没有焦点
/// </summary>
InRangeNoFocus = 2,
/// <summary>
/// Hook得到的消息是目标(控件/窗体)本身发送的数据是否也做进一步处理
/// </summary>
Myself = 4,
All = Sub|InRangeNoFocus|Myself,
}
public enum eMPResult
: int
{
/// <summary>
/// 要求MousePreview不调用CallNextHookEx,直接返回-1,
/// 告诉Windows不要将消息传递到stHookStruct.hWnd去
/// </summary>
CutOffMessage =-1,
/// <summary>
/// 0:要求MousePreview不调用CallNextHookEx, 直接返回0,
/// Windows要将消息传递到stHookStruct.hWnd
/// </summary>
CutOffNextHook = 0,
/// <summary>
/// 要求MousePreview调用CallNextHookEx
/// </summary>
ContinueHook = 1,
}
}
如何实现一个无边框Form的移动和改变大小(二)的更多相关文章
- 如何实现一个无边框Form的移动和改变大小(一)
很多时候我们不希望使用Windows提供的窗体. 我们希望使用一个无边框的窗体,什么border,caption透明就行了. 下面我们来说下一些实现方法. 这个方法要求窗体自定义的border siz ...
- Qt:无标题栏无边框程序的拖动和改变大小
From: http://blog.csdn.net/kfbyj/article/details/9284923 最近做项目遇到的问题,总结下. 有时候我们觉得系统的标题栏和按钮太丑太呆板,想做自己的 ...
- Qt 无标题无边框程序的拖动和改变大小
最近做项目遇到的问题,总结下. 有时候我们觉得系统的标题栏和按钮太丑太呆板,想做自己的标题栏以及最大化.最小化.关闭,菜单按钮,我们就需要 setWindowFlags(Qt::FramelessWi ...
- WINFROM 无边框窗体的移动和改变大小
因为去掉了边框 移动和调整大小都用不了了,可以调用WIN32的API来实现 1.定义必须常量 ; ; ; ; ; ; const int Guying_HTBOTTOMLEFT = 0x10; ; ...
- Qt无边框MainWindow如何拖动四周改变大小
原来还有winEvent(), x11Event() and macEvent() 这些东西...不过貌似还需要找更好的办法,否则就无法跨平台了. 你需要重新处理部分窗体事件,以下代码适用于Windo ...
- Delphi无边框Form拖动
用Delphi做登陆窗口,如果使用无边框Form,想要拖动窗口,可以在某个控件的OnMouseDown事件中写下以下代码 ReleaseCapture; Perform(WM_SYSCOMMAND, ...
- QT: 如何移动和缩放一个无边框窗口
一个QT窗口如下可以做到无边框: Window { id: window //Designer 竟然不支持..., 设计模式时要注意 flags: Qt.FramelessWindowHint wid ...
- 【CITE】 C#中实现拖动无边框Form窗体
首先建一个Windows应用程序 将Form1的 FormBorderStyle属性设置为None 主要是在Form1窗体触发三个事件:Form4_MouseDown,Form4_MouseMove, ...
- WPF实现无边框窗体拖拽右下角▲ 改变窗体大小【framwork4.0】 谢谢大家关注
效果图:(右下角拖拽改变窗体大小) 第一步:添加xaml代码: <Border Name="ResizeBottomRight" MouseMove="Resize ...
随机推荐
- [Java多线程] volatile 关键字正确使用方法
volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性,即多线程环境中,使用 volatile 关键字的变量仅可以保证不同线程读取变量时,可以读到最新修改的变量值,但是 ...
- Apache http server和tomcat的区别
Apache官方网站:http://www.apache.org/Tomcat官方网站:http://tomcat.apache.org/ 1. Apache是web服务器,Tomcat是应用(jav ...
- pyinstaller-py2exe-cx_Freeze打包第一个wxPython程序HelloWorld
pyinstaller 打包hello 7Mb ================= www.pyinstaller.org pip install pypiwin32 pip install pyin ...
- Android如何配置init.rc中的开机启动进程(service)【转】
本文转载自:http://blog.csdn.net/qq_28899635/article/details/56289063 开篇:为什么写这篇文章 先说下我自己的情况,我是个普通的大四学生,之前在 ...
- HDU4850 Wow! Such String! —— 字符串构造
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4850 代码如下: #include <iostream> #include <cst ...
- 电脑设备对于IT人员,犹如武器对于士兵
本人做了多年Softwarer,写些感受. 比我们早的老一代程序员更是用自己的健康总结了一些经验. 先说关于健康方面: 程序员要长期坐着,这对健康损害很大,颈椎腰椎,心肺能力都会衰减.以前只是听说,自 ...
- OSI和TCP/IP
OSI和TCP/IP 1. OSI的七层网络结构(功能及特点) 1) 物理层:为数据链路层提供物理连接,在其上串行传送比特流,即所传送数据的单位是比特.此外,该层中还具有确定连接设备的 ...
- Git基本用法2
二.比较内容 1.比较提交 - Git Diff 现在我们对项目做些修改: $ cd gitproject # 向README文件添加一行 $ echo "new line" &g ...
- tcpdump 探测器分析
注:默认情况下,tcpdump临听它遇见的第一个网络接口,如果它选择了错误的接口,可以-i标志强行指定接口,如果DNS不能用,或者只是不希望tcpdump进行名字查找,请使用-n选项,这个选项(-n) ...
- sipp 对asterisk 进行压力测试
测试环境 asterisk 192.168.106.170 版本astrisk1.8 sipp 192.168.106.141 sipp版本3.3 安装依赖包yum install make g ...