如何实现一个无边框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 ...
随机推荐
- Ubuntu 13.10上用户怎样获得root权限,用户怎样获得永久root权限,假设配置root登录
一.用户怎样获得root权限: 1. 进入terminal 2. 输入sudo passwd root 并设置password,提示要你输入两次password.自己设定password,一定要 ...
- Cena使用
打开cena,在工具-选项中,修改G++和GCC的编译命令.格式:[g++目录]g++.exe %s.cpp -o %s.exe [编译选项]例如以下命令使用刚安装的mingw4.8.1 g++编译, ...
- [Phoenix] 一、快速入门
Phoenix是一个开源的HBASE SQL层.Phoeinx可以用标准的JDBC API替代HBASE client API来创建表,插入和查询HBASE中的数据. Phoenix作为应用层和HBA ...
- 九度OJ 1094:String Matching(字符串匹配) (计数)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1259 解决:686 题目描述: Finding all occurrences of a pattern in a text is a p ...
- runtime之方法的交换
工作中没怎么用到runtime的东西,所以一直没怎么看,现在开始拿起来. runtime之方法的交换: 都知道OC中有category可以对已知类进行扩展,但是假如工程中需要修改某类的原方法,若用ca ...
- BZOJ 2023 [Usaco2005 Nov]Ant Counting 数蚂蚁:dp【前缀和优化】
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2023 题意: 有n个家族,共m只蚂蚁(n <= 1000, m <= 1000 ...
- 存储过程系列五:完整的存储过程备份使用函数REPLACE()substr()
CREATE OR REPLACE PROCEDURE "YLQXSCXKESL_GGXKZ_TO_QB" ( ...
- YII好的博客
http://blog.csdn.net/wzllai/article/details/7659008
- INSTALL_FAILED_UID_CHANGED
ADT试图安装console显示上面的提示.网上查的办法: 1. 删除/data/app/(filename) 文件夹下的apk包 2. 删除/system/app/(filename) 文件夹下的a ...
- python+Django实现Nagios自动化添加监控项目
最近机房刚上了一批机器(有100台左右),需要使用Nagios对这一批机器进行监控.领导要求两天时间完成所有主机的监控.从原来的经验来看,两天时间肯定完成不了.那怎么办?按照之前的想法,肯定是在nag ...