DK的WIN32 API用句柄HANDLE操作Windows窗口,而VC将HANDLE封装为CWnd类的一个成员变量m_hWnd,可以取此对象的m_hWnd属性来得到句柄。同时,VC中每一个窗口类都可以声明为一个窗口指针来调用窗口类的成员属性和成员变量。
窗口是Windows应用程序中一个非常重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口,窗口是指现在是屏幕上面的一快矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接收用户的输入及显示输出。
一个应用程序窗口通常包含标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框、滚动条等
窗口可以分为客户区和非客户区,客户区是窗口的一部分,应用程序通常在客户区中显示文字或绘制图形。而标题栏、菜单栏、系统菜单、最小化和最大化,可调系统边框为窗口的非客户区,他们由windows来管理,而应用程序则主要管理客户区的外观及操作。
窗口可以有一个父窗口,有父窗口的窗口称为子窗口,另外,对话框和消息框也是一种窗口,在对话框上面还包含许多子窗口,这些子窗口的形式有按钮、单选按钮、复选框、组框、文本编辑框等。
此外,在我们启动Windows系统后,我们的桌面也是一个窗口,称为桌面窗口,是位于最上层的窗口,由Windows系统创建和管理。
当用你CreateWindow创建了一个窗口,这个窗口其实并不是你创建的,而是系统替你的创建的,系统为创建这个窗口,它必须要保留很多的和这个窗口相关的数据,这些数据并不是给你用的,而是系统用来维护窗口用的,而句柄就是系统用来找到这个窗口相关数据的一人索引。你向一个API提供一个句柄,比如ShowWindow(HWND);要显示这个窗口,这个工作还是系统替你做的,它根据你提供的索引,也就是句柄,找到窗口,然后进行相关的处理。所以说,句柄是相关对象的唯一索引。从这一点上看,有点像指针,但是指针的内容是对象的地址,而句柄并不是地址,句柄只是一个索引,一个整数。
句柄的实质就是一个结构体的实例。
Windows系统中有许多内核对象(这里的对象不完全等价于"面向对象程序设计"一词中的"对象",虽然实质上还真差不多),比如打开的文件,创建的线程,程序的窗口,等等。这些重要的对象肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为了保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间传递这些数据呢?拖着这成百上千的字节拷贝来拷贝去吗?显然会浪费效率。那么怎么办?当然传递这些对象的首地址是一个办法,但这至少有两个缺点:
I 暴露了内核对象本身,使得程序(而不是操作系统内核)也可以任意地修改对象地内部状态(首地址都知道了,还有什么不能改的?),这显然是操作系统内核所不允许的;
II 操作系统有定期整理内存的责任,如果一些内存整理过一次后,对象被搬走了怎么办?
所以,Windows操作系统就采用进一步的间接:在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。
在Windows系统中,这个编号就叫做"句柄"。
在Windows应用程序中,窗口是通过句柄HWND来标识的,我们要对某个窗口进行操作,首先就要获取到这个窗口的句柄。
每个窗口在被创建出来之后就会被赋予一个句柄,该句柄指向一个数据结构体,结构体里明确表示着该窗口的各种信息,窗口大小,窗口名等,当我们得到这个句柄时就可以请求操作系统对它做一系列操作,列如:移动窗口,关闭窗口,最小化最大化等,这些都是通过窗口句柄来告诉操作系统的,我们要对哪个窗口进行操作,而消息则是告诉操作系统要做什么样的操作,消息的附加参数就是操作值,列如移动窗口,会有附加的xy坐标参数!
句柄有Windows句柄,文件句柄,分配内存句柄,图形句柄等,系统在创建这次资源的时候回为其分配内存,并返回标识这些资源的标识号,这个标识号就是句柄,实际上,我们可以将句柄看做是指针。
在使用句柄之前,必须先创建他们,当不在使用时,应当及时销毁,如果不销毁他们,最终将到时资源泄露,资源泄露有可能导致系统崩溃,所以,务必确保在适当的时候销毁不在使用的句柄。
另外,程序运行时也是以进程存在,进程也是用ID或句柄去标识。
进程创建时,windows系统为进程构造了一个句柄表。当该进程希望获得一个内核对象句柄或者创建一个内核对象从而获得该对象句柄时。系统会将在句柄表中增加一个表项,表项的内容中存储了指向目标内核对象的指针。同时,系统返回这个表项在句柄表中的索引作为句柄。这样,进程就通过句柄查询句柄表得到对象指针,从而可以访问该对象。同时又由于有了句柄表的保护,可以防止对内核对象的非法操作。
windows对象并不是我们平时所说的“面向对象”程序设计中的“类的对象”,而是一种windows资源实体,如画笔、字体等。
如果想要去使用这些windows对象我们需要用句柄来标识它们。
而MFC对象则是真正的“面向对象”思想中的“类的对象”(必须用构造函数去创建)。
在windows编程中,除了普通的“类的对象”外,用得最多的“C++类的对象”应该是MFC对象了(如果你是用MFC编程的话),
MFC对象是指“封装了windows对象的C++对象”(也就是MFC对象中有一个控制window对象的控制器,那么控制器想工作就得和一个window对象链接起来,这样就能控制器就能控制这个对象了)。
所谓Windows对象是Win32下用句柄表示的Windows操作系统对象;
所谓MFC对象就是C++对象,是一个C++类的实例.
一个MFC窗口对象是一个C++ CWnd类(或派生类)的实例,是程序直接创建的。
在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。
而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。
Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在MFC窗口对象的m_hWnd成员变量中。
Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。
MFC中的窗口类,就是C++类与Windows窗口的句柄的结合。在基于CWnd继承而来的所有类中,都有一个公有的成员变量m_hWnd,这个成员变量就是窗口对象关联的windows窗口句柄。我们在类中可以直接使用这个窗口句柄成员变量。这个窗口对象就是标准的C++对象。其实MFC窗口类并不神奇,就是包装了一下API而已。m_hWnd的类型就是HWND。MFC给CWnd提供了两个成员函数Attach(HWND hWndNew )和Detach()。前者传入一个窗口句柄,将该窗口关联到C++对象,后者则是将当前关联的窗口解关联。而关联就是给m_hWnd赋值,解关联就是将m_hWnd设置为NULL。解关联后,窗口对象就不关联任何窗口了,此时就不能执行窗口相关的任何操作,都会失败。如果要关联新的窗口,只要执行Attach函数即可。
4.1 MFC全局函数,如:
AfxMessageBox 显示一个消息框
AfxGetApp 返回应用程序对象CWinApp的一个指针
AfxGetAppName 返回应用程序的名称
AfxInitRichEdit 为应用程序初始化RichEdit控件
4.2 API函数,如:
SendMessage(),调用一个窗口的窗口函数,将一条消息发给那个窗口;
OpenFile() 这个函数能执行大量不同的文件操作;
RegCreateKey() 在指定的项下创建或打开一个项;
GetCaretPos() 判断插入符的当前位置;
ShellExecute() 用指定程序打开指定路径下的文件;
4.3 其父类定义的成员函数,如CWnd类:
PreSubclassWindow()
在调用SubclassWindow之前,允许其它必要的子类化工作
FromHandle()
当给定一个窗口的句柄时,返回CWnd对象的指针。如果没有CWnd对象与这个句柄相连接,则创建一个临时的CWnd对象并与之相连接
GetSafeHwnd
返回m_hWnd,如果该指针为NULL,则返回NULL
etFocus()
要求输入焦点
SetWindowPos()
改变子窗口、弹出窗口和顶层窗口的大小、位置以及顺序
GetClientRect
获得CWnd客户区域的大小
GetDlgItem(),
获得指定的对话框中具有指定ID的控件
UpdateData()
初始化对话框或获得对话框中的数据
SetWindowText()
将窗口的文本或标题文字(如果有)设为指定的文本
SetWindowText
将窗口的文本或标题文字(如果有)设为指定的文本
SetTimer()
安装一个系统定时器,当它被激活时,发送一个WM_TIMER消息
MessageBox()
创建并显示一个窗口,其中包含了应用程序提供的消息和标题
SendMessage()
向CWnd对象发送一个消息,直到这条消息被处理之后才返回
OnPaint()
调用这个函数以重画窗口的一部分
OnLButtonDown()
当用户按下鼠标左键时调用这个函数
OnTimer()
当达到SetTimer指定的时间间隔时调用这个函数
4.4 继承关系不同的GetDlgItem()
1) CWindow::GetDlgItem HWND GetDlgItem(int nID)const; 2) CWnd::GetDlgItem CWnd* GetDlgItem(int nID) const; void CWnd::GetDlgItem( int nID, HWND *phWnd) const; 3)Windows SDK HWND GetDlgItem(HWND hDlg,int nIDDlgItem);
先看下面的两行代码,简单了解一下窗口句柄和指针的一个使用场合:
5.1 API函数以句柄为参数
CString str="在编辑框上显示给定文本。"; SendMessage(GetActiveWindow()->GetDlgItem(IDC_textbox)->m_hWnd, WM_SETTEXT , 0 , (LPARAM)str.GetBuffer(0));
此代码可以在当前活动窗口的IDC_textbox文本框控件中显示一个字符串str。
① API函数SendMessage()功能:将指定的消息发送到一个或多个窗口。
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam)
hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息特定信息。
IParam:指定附加的消息特定信息。
返回值:指定消息处理的结果,依赖于所发送的消息。
② CWnd类方法GetDlgItem(IDC_textbox),能够返回一个MFC指针*CWnd;
③ CWnd类方法GetActiveWindow(),可以返回当前窗口的一个HWND句柄;
④ “->m_hWnd”是将一个指针转换为句柄。一般窗口对象都会有一个其对应的句柄变量,所以我们可以取此对象的m_hWnd属性来得到句柄。
5.2 MFC对象指针调用成员函数
下面的的代码也是在编辑框上显示给定文件:
CString str="在编辑框上显示给定文本。"; CEdit *edit1=(CEdit*)GetDlgItem(IDC_EDIT2); edit1->SetWindowText(str);
GetDlgItem()可以返回一个MFC指针*CWnd,并用(CEdit*)做了下类型转换,然后就可以使用CEdit类的成员函数,例如SetWindowText()。
5.3 API函数使用句柄作为参数
CString str="F:\\help\\list.html"; API函数ShellExecute(this->m_hWnd, "open", str,"", NULL, SW_SHOW);
this->m_hWnd; //返回的就是窗口类自己的句柄呢,也可以用MFC全局函数:
AfxGetMainWnd()->m_hWnd;
对MFC编程来说,对话框和控件都可以理解为一个子窗口,都有对应的一个ID和类。可以返回对应的对象指针。
HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)
HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)
HWND WindowFromPoint(POINT& Point)//获得当前鼠标光标位置的窗口HWND
在任何类中获得应用程序类
HWND AfxGetInstanceHandle()
获取当前活动窗口句柄
HWND GetActiveWindow(VOID)
获取前台窗口句柄
HWND GetForegroundWindow(void);
获得对话框中某控件的句柄
HWND GetDlgItem(m_hDLG,m_nID_DlgItem);
获得GDI对象的句柄
HWND m_hGDIObj=m_pGDIObj->GetSafeHanle();
① 获取当前应用进程的指针
AfxGetApp();
② 获得主框架窗口指针(任何时候都可以用,只要是MFC程序中)
CWnd* pWnd=AfxGetMainWnd();
③ 获得对话框中控件指针
CWnd* pWnd=GetDlgItem(IDC_xxx);
可以强制转换为具体类型,如CEdit *edit1=(*CEdit)GetDlgItem(IDC_xxx);
④ 获得工具栏指针
CToolBar * pToolBar=(CtoolBar*)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_TOOLBAR);
① 指针转化为句柄
在MFC应用程序中首先要获得窗口的指针,然后将其转化为句柄
CWnd *pwnd=FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器 HWND hwnd=pwnd->m_hwnd; //得到它的HWND
这样的代码当开始得到的pwnd为空的时候就会出现一个“General protection error”,并关闭应用程序,因为一般不能对一个NULL指针访问其成员,如果用下面的代码:
CWnd *pwnd=FindWindow(“ExploreWClass”,NULL); //希望找到资源管理器 HWND hwnd=pwnd->GetSafeHwnd(); //得到它的HWND
就不会出现问题,因为尽管当pwnd是NULL时,GetSafeHwnd仍然可以用,只是返回NULL
② 句柄转化为指针
在MFC应用程序中首先用GetDlgItem()获得对话框控件的句柄,然后获得其指针:
HANDLE hWnd; GetDlgItem(IDC_xxx,&hWnd); CWnd *pWnd=CWnd::FromHandle(hWnd); pWnd->Attach(hWnd); //Attaches a Windows to a CWnd object
// 用FindWindow()得到一个窗口句柄,然后转换为容器指针
HWND hWnd=::FindWindow(NULL,_T("IDD_Assistants")); //得到对话框的句柄 CAssistantsDialog* pWnd=(CAssistantsDialog*)CAssistantsDialog::FromHandle(hWnd); //由句柄得到对话框的对象指针 pWnd- >xxx( ); //调用C***Dialog中的函数xxx();
ID->句柄,hWnd=::GetDlgItem(hParentWnd,id);
ID->指针,CWnd::GetDlgItem();
句柄->ID,id=GetWindowLong(hWnd,GWL_ID);
指针->ID,id=GetWindowLong(pWnd->GetSafeHwnd,GWL_ID);
HICON->ID,HICON hIcon=AfxGetApp()->LoadIcon(nIconID);
ID->HICON,HICON hIcon=LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(nIconID));
-End-
当下,构建交互式应用程序的主流技术是 Web 技术,其中包括 HTML、CSS 与 JavaScript。
在过去的 10 年,Web 技术生态发生了翻天覆地的变化,包括层出不穷的开发框架,诸如 React、Vue、Svelte,也包括日新月异的前端工程化工具,比如 Webpack、esbuild、Vite 等等。
但归根结底,他们都逃不开 HTML、CSS、JavaScript 三剑客的范畴。
Web 技术生态成熟、稳定,然而却存在一个致命的问题:使用 Web 技术去构建跨平台应用程序并不是一件简单的事情。
这也是为什么许多平台特定的框架(platform-specific frameworks)与跨平台框架(cross-platform frameworks)依然受到欢迎的原因。
比如其中最著名的跨平台框架 Flutter,它部分基于浏览器引擎的技术,实现了「编写一次,全平台运行」的目标。而且这些框架,也基本不使用 HTML、CSS 这些 Web 技术。这是为什么呢?
因为 HTML 诞生的目的问题,以及 HTML 与 CSS 的开发体验问题。
HTML 即超文本标记语言,最初是三十年前为了制作可链接的文档而发明的,而不是为了做应用程序。它更多是一种标记而不是一种语言。大多数人甚至都不将编写 HTML 视为编程,因为它根本不是一种编程语言。
直到出现 HTML5 (通常被称为 H5)、CSS3 和 ES5 版本之后的 JavaScript,人们才逐渐开始用这些技术制作 Web 应用程序。在那之前,HTML 只是用于完成他最开始的目的。
但做成 Web 应用的可行性,最大还是来自于 JavaScript 性能的提升。
上面是 Lin Clark 介绍 JavaScript 性能历史的一张图。从2008年开始,JavaScript 性能就开始飞速提升。这对于应用程序的最终用户来说有巨大的好处,因为做出来的应用程序终于不卡了,甚至可以对性能有所期待了。
但是,对于开发者来说,仍然逃不开编写 HTML+CSS。
就算使用一些前沿的前端框架,如 React、Vue、Angular 等,我们仍然需要编写类似 HTML 的代码,并仔细调整 CSS 或者 CSS 预处理器(如 SCSS、Saas)的样式表。
这缓慢、枯燥、而且乏味。
太多的人力、时间被浪费在实现图形用户界面的细节上,使用一些并不是一开始就为了 UI 而设计的技术。这导致开发者经常要来回调整样式、处理浏览器兼容性问题、应用奇怪的 CSS 技巧、避开性能陷阱等等。
另外,还需要在过度发展的 NPM 生态系统中,使用那些复杂的前端工程工具来进行应用程序的构建。这个过程效率也非常低下,开发体验非常痛苦。更不要说 Web 应用在跨平台需求中会遇到更多的陷阱,比如平台兼容性、体积大小、性能问题,等等。
此刻,我们质疑,坚持使用 HTML 和 CSS 的理由到底是什么?
然后我们再回过头来看看其他的非 Web 框架。
Electron 首先被我们排除。虽然微软用它做出了 VSCode 这样成熟的跨平台应用程序,但也投入了巨大的成本,并且一般开发者可没有这么雄厚的财力。
但最关键的是,VSCode 其实是用 Web 技术做出来的,Electron 只是帮助它做成了跨平台应用而已。
看看我们还有什么其他选择:
随着近年来 Web 应用的比例不断增加,桌面端应用逐渐式微。但正是因为 Web 应用在跨端上的致命问题,这些非 Web 框架仍有一席之地,并且看上去也具有不可替代性。
当然,其中的某些年代过于久远的开发框架,开发人员的体验甚至比编写 HTML 更糟糕,因为他们可能被迫编写类似于这样的命令式和面向对象的代码。
var count=0
let stack=new VStack
let text=new Text("Count: \(count)")
stack.add_child(text)
let button=new Button("Increment")
button.set_onclick(||
count +=1
text.set_text("Count: \(count)")
)
stack.add_child(button)
不是编写声明式且响应式的代码,就像程序员一直梦寐以求的这样:
struct AppState {
count: i32
}
VStack {
Text("count: \(state.count)")
Button("Increment") {
state.count +=1
}
}
这就是为什么 Flutter 看起来像是开发应用程序的灵丹妙药:
不过也还是有开发者不喜欢 Flutter,因为它引入了另一种新的、陌生的语言 Dart,以及额外的虚拟机负担。
Flutter 真正的问题在于与现有生态系统的兼容性,因为人们倾向于更喜欢重用已建立的资源和维护成熟的应用程序。编程语言也是出于同样的原因。
为了解决 Flutter 的一些问题,有些优秀开发者们尝试开发了 Flutter 的 JavaScript 版本,虽然后来失败了。因为 Flutter 本身正在迅速迭代,以至于两者无法融洽。不过这部分的工作导致诞生了 Kraken 框架,它允许编码人员编写 HTML,并使用 Flutter 引擎进行跨平台渲染。
等等...发生了什么?在非 Web 框架中再次编写 HTML?
不!再也不想写 HTML 了!
尽管如此,我们不得不承认,HTML+CSS 是表示 UI 的一个很好的组合,因为:
但是在实践中,UI 的工程有时是没有意义且不必要的。假设我们已经有了设计师提供的高保真设计原型,编码人员需要做的是:
第一部分总是让人头疼的源头:涉及大量的细节、耗时、需要与设计师进行讨论反复沟通,沟通成本很高,如果设计更新,代码也需要更新,也许还需要另一场“昂贵”的讨论。
更不用说,这种工作通常被视为费时费力没技术含量工作,也因此就有了大家听到的前端程序员通常被其他非前端程序员所鄙视。
一些聪明的开发者想出了使用编译器技术或更具体地说是转换器技术来实现设计到代码的解决方案,将整个高保真设计转换为机器生成的 HTML+CSS 代码。这个就是所谓的 Design to Code。
但它是为产品经理和设计师量身定制的,而不是为开发人员。这种内在问题包括但不限于:
总之,从开发者的角度来看,Design to Code 不是一个好的技术解决方案。
现在让我们看看什么是 Design as Code。
在 VGG 所倡导的 Design as Code 开发流程中,用户可以在某种程度上抛弃 HTML+CSS。
因为设计稿完全替代了 HTML+CSS 的角色,设计就是代码。请记住 VGG 的第一性原则:通过消除冗余的开发工作来弥合设计与开发两方之间的巨大鸿沟。
很明显的优势:图形用户界面的设计和开发只需要进行一次,因为两者实际上是一体的。因此两者的摩擦、讨论会减少,这让双方都更高效。
对于开发人员来说,他们能够直接在设计文件上编写业务逻辑,然后由 VGG 将其运行为一个交互式图形应用程序。这可以节省大量重复的工作,不仅提高了开发人员的工作效率,也为整个团队增加了工作效率。
Design as Code 的想法很简单,但它实现起来非常困难,比如会遇到来自编译器技术、编程接口抽象和图形渲染方面的工程挑战。
因此,为了实现 VGG 的 Design as Code 开发流程
VGG 天生就有着对开发生态很好的兼容性。相比于低代码,VGG 是一套真正为开发者设计的工具。小结:
基于以上两点,VGG 以及它主打的 Design as Code 可以为现代交互式应用开发带来巨大的好处。在最终用户对用户界面的实际使用效果没有明显感知差异的前提下,大大提升了应用开发者的开发体验,甚至可以让设计师、产品经理来承担一部分的界面开发工作:无非是把现有的设计工具当作一个 UI Builder 来使用罢了。
下面引用来自 VGG Github 首页的一张图,来更好地说明 Design as Code 的概念与 VGG 的有机组成:
在这篇文章中,我们讨论了 Web 技术、特定平台的框架、跨平台框架、Design to Code 解决方案以及基于 VGG 开源引擎的 Design as Code 开发流程。
我们提出了 Design as Code 的概念,介绍了 VGG 作为一个全新的开发交互式图形应用程序的框架。但 VGG 仍然年轻,因为还有许多技术挑战需要克服,VGG 运行时引擎现已开源,欢迎大家一起参与 VGG 开源社区共建。
关于 VGG 的更多细节将在后续的文章中讨论。如果您感兴趣,可以继续阅读官方博客和文档
https://blog.verygoodgraphics.com/
现在大部分公司客户端的开发基本上都选用Qt,很多以前用mfc写的软件也慢慢的用Qt重构,那么选Qt的原因是什么?Qt相比mfc的优势又是什么呢?我认为有以下几个原因:
首先Qt跨平台,除了正常软件在多个平台下能运行外,漂亮国对我国围追堵截的今天,跨平台这个特性对我们很重要,要在多个平台下运行同一个软件,避免对我们的卡脖子;
其次QT做的GUI开发要比MFC要好,并且QT界面库支持CSS,界面设计更方便更美观,界面设计美工可以直接写;
面向对象的特性体现的比MFC明显,在命名,继承,类的组织等方面保持了优秀的一致性,代码写起来比较优雅;
还有就是个人感觉Qt上手比mfc要快很多,要是不太麻烦的功能,用Qt实现起来比较方便,对新手更友好,我觉得这也是选择Qt的一个很重要的原因,Qt现在功能越来越强大,做非GUI开发也挺好。再说了微软已经停止对mfc的更新了,这也是软件公司换到其他库的一个原因吧。
*请认真填写需求信息,我们会在24小时内与您取得联系。