微软提供的Winnet类是一个应用层的网络通信组件,
它可以使你的应用程序很容易的实现http、ftp、gopher等协议而不需要
你去深入的了解协议本身的规范。而之前,如果要想做类似的应用,我们
必须了解socket编程并且要对协议本身非常熟悉,哪怕是一个非常非常
简单的程序。
下面是codeguru上的一个使用wininet类的例子,它能够从给定的url地址中
获取该文件。
这个例子实现了两个方法:
cstring getwebpage(const cstring& url);
void seterrormessage(cstring s);
getwebpage是主要的方法,后面跟的url地址必须是一个完整的url地址,
比如http://blog.csdn.net/cqq。
seterrormessage方法, 能够处理程序中发生的错误。
下面来看代码:
/*
//------------------------------------------------------------------------------------------------------------------
// webworld.h: interface for the cwebworld class.
//一个封装的类cwebworld,webworld.h是该类的头文件
//------------------------------------------------------------------------------------------------------------------
*/
#include "wininet.h" //包含wininet.h ,在mfc中也可以是afxinet.h
class cwebworld
{
public:
void seterrormessage(cstring s); //声明seterrormessage方法
cstring getwebpage(const cstring& url); //声明getwebpage方法
cwebworld(); //构造函数
virtual ~cwebworld();
private:
cstring m_errormessage;
hinternet m_session; //声明一个http连接的句柄
};
/*
//------------------------------------------------------------------------------------------------------------------
// webworld.cpp: implementation of the cwebworld class.
// cwebworld类的具体实现
//------------------------------------------------------------------------------------------------------------------
*/
#include "stdafx.h"
#include "webthief.h"
#ifdef _debug
#undef this_file
static char this_file[]=__file__;
#define new debug_new
#endif
#define agent_name "codegurubrowser1.0"
//////////////////////////////////////////////////////////////////////
// construction/destruction
//////////////////////////////////////////////////////////////////////
cwebworld::cwebworld()
{
dword dwerror;
// initialize the win32 internet functions
// 构造函数中初始化网络连接
m_session = ::internetopen(agent_name,
internet_open_type_preconfig, // use registry settings.
null, // proxy name. null indicates use default.
null, // list of local servers. null indicates default.
0) ;
dwerror = getlasterror();
}
cwebworld::~cwebworld()
{
// closing the session
//虚构函数中释放连接
::internetclosehandle(m_session);
}
cstring cwebworld::getwebpage(const cstring& url)
{
hinternet hhttpfile;
char szsizebuffer[32];
dword dwlengthsizebuffer = sizeof(szsizebuffer);
dword dwfilesize;
dword dwbytesread;
bool bsuccessful;
cstring contents;
// setting default error message
contents = m_errormessage;
// opening the url and getting a handle for http file
hhttpfile = internetopenurl(m_session, (const char *) url, null, 0, 0, 0);
if (hhttpfile)
{
// getting the size of http files
bool bquery = ::httpqueryinfo(hhttpfile,http_query_content_length, szsizebuffer, &dwlengthsizebuffer, null) ;
if(bquery==true)
{
// allocating the memory space for http file contents
dwfilesize=atol(szsizebuffer);
lpstr szcontents = contents.getbuffer(dwfilesize);
// read the http file
bool bread = ::internetreadfile(hhttpfile, szcontents, dwfilesize, &dwbytesread);
if (bread)
bsuccessful = true;
::internetclosehandle(hhttpfile); // close the connection.
}
}
else
{
// connection failed.
bsuccessful = false;
}
return contents;
}
void cwebworld::seterrormessage(cstring s)
{
m_errormessage = s;
}
下面是关于上面的类的使用方法:
cwebworld a;
cstring pagecontent;
作者:经乾
一、Windows的控制面板应用程序
---- 在Windows的系统目录下可以找到控制面板应用程序,它们是一些扩展名
为cpl的dll,通常用来提供配置服务,如配置网络硬件和软件用的ncpa.cpl,配
置桌面用的desk.cpl等,它们的父窗口都是桌面。运行这类程序的方法很多:双
击控制面板中的图标,双击系统目录下的cpl文件,或者使用如下DOS命令:
rundll32 shell32.dll,Control_RunDLL
< cpl文件> [@n]或者
control < cpl文件> [@n]
---- 其中@n用于指定运行运行哪一个小程序(一个cpl文件中可包含多个小程
序),若不带此参数则相当于@0,即运行第一个小程序。例如:
---- Rundll32 shell32.dll,Control_RunDLL main.cpl @n
---- 若不带@n参数,则运行鼠标设置;@1则运行键盘设置;@2则运行打印机设
置;@3则显示系统字体。
---- Control_RunDLL是shell32.dll中定义的一个函数,大小写敏感,用于启动
控制面板。
---- 通过控制面板应用程序来提供配置服务是一种很好的方法,那么如何设计
这种应用程序呢?下面以Visual C++6.0为例进行介绍。
二、开发控制面板应用程序
---- 若希望一个文件传输程序启动后自动连接到某一个站点,就需要为其提供
一些缺省值,如:服务器名、用户名、口令等。下面就开发一个控制面板应用
程序来提供这些缺省值,具体步骤为:
---- 1.创建一个"MFC AppWizard (dll)"类型的项目,命名为:MyCplApp,选
择"Regular DLL With MFC statically linked",使它的运行不依赖于其它任何DLL。
---- 2.选择Project菜单下的Settings项或按Alt+F7,在Link页中将输出文件
名改为:
---- < WindowsDir >/< SystemDir >/Mycplapp.cpl,在Debug页中将
"Executable for debug session"改为:< WindowsDir >/< SystemDir >/Control.exe,
以便直接用控制面板运行。对于Win9x,< SystemDir >为System,对于WinNT,
< SystemDir >为System32。
---- 3.从MSDN Library Visual Studio 6.0光盘的Smples目录中找到
Ctrlpan.cpp 和Ctrlpan.h 文件,并把它们加到项目中。
---- 4.创建如图所示对话框,代号为:IDD_MYDIALOG,用Class Wizard创建
一个CMyDialog类。为每个控件创建成员变量,如"FTP服务器"对应的成员变
量定义为:CString m_strServer。
---- 5.引入或新建一个ICON,代号为:IDI_MYICON,用于控制面板中显示。
---- 6.从CControlPanel类(在Ctrlpan.h中定义)继承一个新类CMyPanel,修改
MyPanel.h文件:
#include "Ctrlpan.h"
class CMyPanel : public CControlPanel
{ public:
virtual LONG OnInquire(UINT uAppNum, NEWCPLINFO* pInfo);
virtual LONG OnDblclk(HWND hwndCPl,
UINT uAppNum, LONG lData);
};//两个虚拟函数由读者加入
---- 7.在MyPanel.cpp中实现MyPanel.h中定义的两个虚拟函数:
LONG CMyPanel::OnInquire(UINT uAppNum,
NEWCPLINFO* pInfo)
{ //此函数在控制面板打开时被调用,
用于获取资源信息,即填充pInfo结构
pInfo- >dwSize = sizeof(NEWCPLINFO);//指定结构长度
pInfo- >dwFlags = 0;//此成员忽略
pInfo- >dwHelpContext = 0; //此成员忽略
pInfo- >lData = 0;//小程序传递给应用程序的LONG类型的值
pInfo- >hIcon= ::LoadIcon(AfxGetResourceHandle(),
MAKEINTRESOURCE(IDI_MYICON));//加载图标
strcpy(pInfo- >szName, "FTP设置");//设置名称
strcpy(pInfo- >szInfo, "设置FTP缺省信息");//设置描述
strcpy(pInfo- >szHelpFile, "");//此成员忽略
return 0; //不发送CPL_INQUIRE消息
}
LONG CMyPanel::OnDblclk(HWND hwndCPl,
UINT uAppNum, LONG lData)
{ //双击控制面板中的图标时,此函数被调用,
用于读取或保存设置信息。
CMyDialog dlg(CWnd::FromHandle(hwndCPl));
//用父窗口句柄初始化对话框
HKEY hcpl;//主键,用于读写注册表
if (RegOpenKeyEx(HKEY_CURRENT_USER,
"Control Panel//FTPSet",0,
KEY_QUERY_VALUE, &hcpl) == ERROR_SUCCESS)
{ DWORD dwType = 1;//字符串类型REG_SZ
DWORD dwSize;//字符串长度
RegQueryValueEx(hcpl,"FTPServer",NULL,&dwType,
(BYTE*)(LPCTSTR)dlg.m_strServer,&dwSize);
RegCloseKey(hcpl);
}//查询注册表,读取以前的设置信息,上面仅以m_strServer为例。
RegCloseKey(hcpl);
if(dlg.DoModal()!=IDOK) return 0;//执行对话框,
若用户点击"取消"则返回
DWORD dwDisp;//用于接收创建主键的返回值
if (RegCreateKeyEx(HKEY_CURRENT_USER,
"Control Panel//FTPSet",0,"",
REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
&hcpl, &dwDisp) == ERROR_SUCCESS)
{ RegSetValueEx(hcpl,"FTPServer",0,REG_SZ,
(BYTE*)(LPCTSTR)dlg.m_strServer,
dlg.m_strServer.GetLength());
RegCloseKey(hcpl);//将对话框中设置保存到注册表
}return 0;
}
---- 8.修
VC5开发工具提供了现成的窗口、控制与工具条的制作手段,大大简化了界面的开发
过程,并且使得开发出的界面具有组态软件风格,使用起来方便、灵活、简单易学。
本文以一实例介绍如何实现三个独立的分离窗口:监视窗口,控制窗口和动画窗口,
并以图1中的进水和温度值传递为例,介绍如何实现控制功能和不同窗口间的数据共享,
并介绍实现无闪烁动画的方法。
(图略)
如图1,将工作台分离成为三个窗口,动画窗口用于模拟锅炉的进、出水、升温的画
面显示,其中的画面与系统采集的数据相对应。控制窗口用于实现预设温度值,调节水位、
控制加热、暂停等功能。监视窗口用来实时跟踪采样的温度值,作出温度--时间曲线。
一、创立分离窗口
要实现多窗口显示,必须使用CSplitterWnd类,将窗口分成三个子窗口,然后将各个
功能类与窗口联系起来。在创建应用程序时,在第一步中选择Single Document Interface, 并
选用中文字库,在第4步中按下Advanced,选择Use Split Window选项。设定应用程序名
为Animation。目前我们只有一个视类CAnimationView,它将与动画窗口对应,此外我们
还要生成具有对话功能的监视窗口(对应CShowView类)和控制窗口(对应 CControlView
类)。在Resource View中调出上下文菜单并选择Insert,选择属性为IDD_FORMVIEW,创
建监视对话框IDD_SHOWVIEW和控制对话框IDD_CONTROLVIEW,并单击鼠标右键在
Properties选择项中选择中文字库。然后编辑IDD_CONTROLVIEW:利用VC5提供的控件
生成器生成ID名为IDC_SETTEMPERATURE的文字编辑域,并生成Caption为“设置温
度初始值”。再利用button生成器,生成控件IDC_WATERIN,IDC_CONFIRM,Caption
分别为“进水”和“确认”。再利用ClassWizard,创建基于CFormView类的新类,分别为
CShowView和CControlView类,并将它们与刚创建的两个对话框联系。
在CMainFrame类中,重载OnCreateClient()函数创建三个静态分离窗口,先在
MainFrame.h中声明所需变量:
protected:
CSplitterWnd m_wndSplitter2;
转入MainFrame.cpp程序,在开头处包含头文件“ShowView.h”,向下找到函数
OnCreateClient(),添加如下代码,生成两个窗口,三个视图:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT/*lpcs*/,CCreateContext* pContext)
{
//先分裂成两个窗口,一行两列
if(!m_wndSplitter.CreateStatic(this,1,2))
{
TRACE0("Failed to CreateStaticSplitter/n");
return FALSE;
}
// 加上动画窗口,将其放在左边
if(!m_wndSplitter.CreateView(0,0,pContext->m_pNewViewClass,CSize(425,50),pContext))
{
TRACE0("Failed to create first pane/n");
return FALSE;
}
//将第二个窗口再一分为二
if(!m_wndSplitter2.CreateStatic(
&m_wndSplitter, //原来的m_wndSplitter是父指针,m_wndSplitter2是子指针
2,1, //窗口分为两行,一列
WS_CHILD|WS_VISIBLE|WS_BORDER,m_wndSplitter.IdFromRowCol(0,1)))
{
TRACE0("Failed to create nested splitter/n");
return FALSE;
}
//增加两个视图,并调整视图大小
if(!m_wndSplitter2.CreateView(0,0,
RUNTIME_CLASS(CShowView),CSize(0,175),pContext))
{
TRACE0("Failed to create second pane/n");
return FALSE;
}
if(!m_wndSplitter2.CreateView(1,0,RUNTIME_CLASS(CControlView),
CSize(0,0),pContext))
{
TRACE0("Failed to create third pane/n");
return FALSE;
}
return TRUE;
}
再转入Animation.cpp中,修改InitInstance()函数,将其中的m_pMainWnd->ShowWindow
(SW_SHOW),改为m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);至此,我们
可以生成图1的界面框架。
二、动画显示窗口的实现
动画是通过调用一幅幅的图片来实现的,因此先将所需的画面载入资源BITMAP中,
并按顺序编辑它们的ID号,然后在定时器中,每隔一定的时间调用一次动画函数。第一
步先生成定时器,用ClassWizard给CAnimationView添加消息处理程序:OnCreate()函数
对应于消息WM_CREATE,OnTimer()函数对应于消息WM_TIMER。编辑函数OnCreate(),
生成每隔0.1秒的时钟。
int CAnimationView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CView::OnCreate(lpCreateStruct)= = -1)
return -1;
SetTimer(2,100,NULL); //产生每隔0.1秒的时钟
return 0;
}
在函数OnTimer()中,调用动画服务函数ServicedAnimation(),该函数根据系统情况作出
无闪烁动画,并可以根据不同的功能,选择画面。
void CAnimationView::OnTimer(UINT nIDEvent)
{
CClientDC ClientDC(this);
ServicedAnimation(&ClientDC); //调用动画服务函数
CView::OnTimer(nIDEvent);
}
ServicedAnimation()用于检查系统的时钟,并能计算从发生最后一个动画事件开始计算
起所经过的时间,然后这个函数检查本帧动画的延迟时间,并决定是否到达了另一次更新
的时间,
---- 读 了 贵 报9 月7 日《 学 习 与 编 程》 版 刊 登 的 宋 立 波 先
生 的 文 章, 本 文 介 绍 另 一 种 方 法 来 制 作 封 面, 此 方 法 对
于 单 文 档、 多 文 档 及 对 话 框 窗 体 均 适 用, 且 弥 补 了 宋 立
波 先 生 文 章 中 所 述 封 面 关 闭 后 应 用 程 序 窗 体 才 能 显 示
的 不 足 之 处。 方 法 如 下:
---- 1. 用App Wizard 生 成 一 工 程, 然 后 打 开 资 源 编 辑 器,
Import 一 个BMP 图 形 文 件( 注: 由 于VC 的Bitmap 资 源 只 能 使
用256 色 图 形, 所 以 需 用 画 板 或 其 他 图 形 工 具 转 换 成256
色 图 形), 打 开 属 性 对 话 框, 将ID 号 改 为IDB_BITMAP -SPLASH。
---- 2. 新 建 一 个 对 话 框 资 源, 打 开 属 性 对 话 框, 选 择
General; 将 新 对 话 框 资 源 的ID 号 改 为IDD_DIALOG_SPLASH, 选
择Styles, 将Title bar 选 择 框 置 空, 改 变Border 属 性; 选 择
None, 则 封 面 为 平 面 图 形; 选 择Dialog Frame, 则 封 面 为 立
体 三 维 图 形。 在 对 话 框 资 源 上 添 加Picture 控 件, 打 开 属
性 对 话 框 选 择General, 将Picture 控 件 的Type 改 为Bitmap、 Image
改 为IDB -BITMAP -SPLASH; 将 对 话 框 资 源 的 大 小 改 为 与
Picture 控 件 大 小 相 同。
---- 3. 选 对 话 框 资 源IDD_DIALOG_SPLASH, 打 开Class Wizard 添 加
一 个 新 类, 类 名 定 义 为CSplash。
---- 4. 在MainFrame.h 的 类 定 义 中 添 加 一 指 针 成 员 变 量
*sphash, 类 型 为CSplash; 在MainFrame.cpp 文 件 中 添 加 头 文 件,
引 用 #include“splash.h”。 打 开Class Wizard, 选 择Message Maps,
在Class name 中 选 择CMainFrame, 在Messages 中 选 择WM_TIMER 添 加
OnTimer 函 数。
---- 5. 在CMainFrame 类 的OnCreate 函 数 中 添 加SetTimer 函 数, 并
添 加 显 示splash 对 话 框 的 程 序 段:
int CMainFrame::OnCreate
(LPCREATESTRUCT 1pCreateStruct)
{
SetTime(1,50,NULL);
---- ∥ 添 加ID 号 为1 的WM_TIMER 事 件, 响 应 频 率 由 用 户 自 定 义, 但 不 宜 过 大
splash=new CSplash();
∥ 添 加 播 放WAV 声 音 的 程 序 段
∥ 例 如:sndPlaySound(“sound.way,”SND_ASYNC);
splash ->Create(IDD_DIALOG1);
splash ->ShowWindo(SW_SHOW);
splash ->UpdateWindow();
… … ∥ 此 处 是OnCreate 函 数 中 原 有 的 程 序 段
CenterWindow();
∥ 应 用 程 序 窗 体 居 中 显 示
return 0;
}
---- 6. 在CMainFrame 类 的OnTimer 函 数 中 添 加 响 应WM_TIMER 事 件
的 程 序 段: Void CMainFrame::OnTimer(UINT nIDEvent)
{
∥TODO:Add your message handler code here and/or call default
if (nIDEvent==1)
{
if(splash ->Is WindowVisibl())
{
splash ->SetActiveWindow();
∥ 封 面 置 为 当 前 活 动 窗 体
splash ->UpdateWindow();
Sleep(2000); ∥ 封 面 显 示 停 留 时 间 由 用 户 自 己 改 变
splash ->SendMessa(WM_CLOSE);
}
else
{
SetActiveWindow();
∥ 应 用 程 序 窗 体 置 为 当 前 活 动 窗 体
KillTimer(1);
∥ 清 除WM_TIMER 事 件
}
}
}
---- 到 此, 封 面 制 作 成 功, 如 果 要 添 加 声 音, 只 需 按 宋
立 波 先 生 文 章 中 所 述 方 法, 添 加 在CMainFrame 类 的OnCreate
函 数 中 注 释 段 内 即 可。 此 法 制 作 简 单, 容 易 实 现, 唯 一
的 缺 点 是 此 封 面 显 示 的 图 形 色 彩 不 够 丰 富。( 贾 暾)
很 多 大 型 应 用 程 序 都 有 启 动 封 面, 如Word 等 办 公 系 列 软
件 和VC + + 等编 程 软 件。 通 过 启 动 封 面, 除 了 显 示 应 用 程
序 名 称 和 版 权 等 提 示 画 面,还 可 避 免 由 于 应 用 程 序 启 动
前 进 行 大 量 数 据 初 始 化 时, 用 户 较 长 时 间 的等 待, 给 应
用 程 序 增 添 了 许 多 动 态 特 性 和 专 业 规 范。 鉴 于VC + + 开
发 工 具 应 用 较 广, 这 里 以VC5 为 例 阐 述 启 动 封 面 的 一 般
实 现 步 骤。
---- 1. 制 作 封 面 位 图
---- 制 作 应 用 程 序 启 动 封 面 真 彩 位 图, 记 录 位 图 的 高 度
和 宽 度, 建 立 所 需要 的 其 他 声 音 等 文 件。
---- 2. 建 立 应 用 程 序
---- 利 用FILE
随着Microsoft凭借Windows在操作系统上取得的巨大成绩,Windows用户界面也
日益成为业界标准。统一的界面给广大用户对应用软件的学习与使用带来了很大
方便。但每天都面对同一副面孔,日久天长难免会产生一些厌倦,开发一些"离
经叛道",一改Windows应用程序千篇一律的"标准"界面,一定会给你带来一种清
新的感觉。
---- 标准Windows应用程序窗口一般为带有标题栏的浅灰色矩形外观,因而"异
形"对话框/窗口也主要是颜色与外形上动手脚。
---- 1.改变背景颜色
---- 改变对话框(窗口)的背景颜色是最简单的改变Windows应用程序外观的
方法,根据Windows创建与管理机理,一般有两种方法。一种是处理WM_CTLCOLOR
消息,首先创建所选背景颜色的刷子,然后调用SetBkColor()或
SetDialogBkColor()以所创建的刷子来绘制窗口或对话框的背景。需要重画窗
口或对话(或对话的子控件)时,Windows向对话发送消息WM_CTLCOLOR,应用程
序处理WM_CTLCOLOR消息并返回一个用来绘画对话背景的刷子句柄。另外一种是
响应Windows的WM_ERASEBKGND消息,Windows向窗口发送一个WM_ERASEBKGND消息
通知该窗口擦除背景,可以使用VC++的ClassWizard重载该消息的缺省处理程序
来擦除背景(实际是用刷子画),并返回TRUE以防止Windows擦除窗口。
---- 2.改变窗口外形
---- 通过使用新的SDK函数SetWindowRgn(),可以将绘画和鼠标消息限定在窗
口的一个指定的区域,因此实际上是使窗口成为指定的不规则形状(区域形状)
。"区域"是Windows GDI中一种强有力的机制,区域是设备上的一块空间,可以是
任意形状,复杂的区域可以由各个小区域组合而成。Windows内含的区域创建函
数有CreateRectRgn()、CreatePolyRgn()、CreatePolygonRgn()、
CreateRoundRectRgn()和CreateEllipticRgn(),再通过CombineRgn()来组合
区域,即可得到复杂形状的区域,获得复杂形状的窗口外形。
---- 通过上面的方法虽然可以得到"异形"窗口,但感觉颜色单调,外形也
不够"COOL",能否获得更酷的"异形"对话框/窗口呢?回答是肯定的。下面就
介绍利用位图和蒙板创建"异形"对话框/窗口的方法。
---- 3.利用位图创建异形对话框窗口
---- 利用位图创建异形对话框原理是根据象素的颜色来进行"扣像"处理,对所有
非指定颜色象素区域进行区域组合。利用这一技术,实际上就是实现对话框/窗
口的位图背景,并且对指定的颜色区域进行透明处理。
---- 下面就以透明位图为背景的对话框为例来说明:(略)......
---- 4.进一步的讨论
---- 前面实现了单一模式的异形对话框,但有些情况下又需要不同的样式,如
有标题栏、边框等,或者只作局部的处理,这就是前面两个成员变量
m_FrameWidth和m_CaptionHeight作用,通过在OnInitDialog()判断窗口样式,
使m_FrameWidth和m_CaptionHeight取不同的值。这部分的代码为:......
---- 5.结束语
---- 这种异形窗口的创建不仅适应于对话框,而且适应于所有的基于CWnd类的
派生窗口。采用这一方法,你可以创建出任何只要你能够画出的窗体,实现只要
可以画出,就可以做出的目标。
---- 本文代码在Visual C++ 5.0、6.0下调试通过,运行正常,操作系统为
Windows98SE。
随着Windows95的推出,在PC系统中出现了越来越多的应用程序采用了非矩
形外观的窗体,或者模拟现实中的事物,如钟、眼睛等;或者创造一个具有三维
观感的非现实物体,这类程序以各种mp3播放器为代表,甚至一些大腕级的老牌
应用程序(如Norton),也开始拥有这种窗口。一来是因为Windows操作系统和
各类开发工具自身功能的极大提升;二来,也说明开发人员希望通过与众不同的
外观,来强调使用时的第一映像,以期达到吸引用户的目的,毕竟,现在的PC是
一个充满图形(图象)的世界,充分利用这一特点,也能在一定程度上改善程序
界面的可操作性。而且,对于同一类型的应用程序,在功能、性能相差不大的情
况下,用户也往往愿意选择外观漂亮的那种。因而,很多文章都在介绍如何创建
不规则的窗口,但几乎千篇一律地基于VB进行说明。而笔者本人一直基于C/C++
语言进行开发,因此,研究了一下在VC++5中实现不规则窗口的方法,下面就实
现的主要方法进行说明。
VC++5提供了CRgn类和SetWindowRgn()函数来实现不规则的程序窗口。创建
一个不规则窗口的过程是:首先定义一个CRgn类,并用各种初始化函数创建CRgn
类的具体区域,然后调用CWnd::SetWindowRgn()函数创建不规则窗口。
CRgn是从CgdiObject衍生出来的类,用来确定一个多边形、椭圆或者由多边
形及椭圆合成的范围,在程序中主要会用到CreateRectRgnIndirect()、
CreateEllipticRgnIndirect()、CreatePolygonRgn()三个函数。
CreateRectRgnIndirect(LPCRECT
lpRect)函数创建一个矩形区域,参数lpRect指定所创建的矩形区域在窗口用户区
中的left(左)、top(上)、right(右)、bottom(下)坐标。例如:
CRgn MyRgn;
RECT m_rect;
m_rect.left=0; m_rect.top=0; m_rect.right=500; m_rect.bottom=300;
MyRgn.CreateRectRgnIndirect( &m_rect );
CreateEllipticRgnIndirect(LPCRECT
lpRect)函数创建一个椭圆形区域,参数lpRect指定所创建的椭圆形区域在窗口
用户区中的left(左)、top(上)、right(右)、bottom(下)坐标,如果指
定right坐标与left坐标之差等于bottom坐标与top坐标之差,则创建的区域是一
个圆。例如:
CRgn MyRgn;
RECT m_rect;
m_rect.left=0; m_rect.top=0; m_rect.right=500; m_rect.bottom=300;
MyRgn.CreateEllitpticRgnIndirect( &m_rect );
CreatePolygonRgn(LPPOINT lpPoints, int nCount, int
nMode)函数创建一个多边形区域,参数lpPoints指向一个POINT结构数组,
在POINT结构数组中每个POINT结构项,用来确定多边形顶点在窗口用户区中的
坐标;nCount说明POINT结构数组中POINT结构项的数目,也就是多边形的顶点
数;nMode指定多边形的填充方式,一般使用ALTERNATE方式。例如创建一个三
角形:
CRgn MyRgn;
POINT Points[3];
Points[0].x=Points[0].y=0; Points[1].x=10; Points[1].y=30; Points[2].x=5; Points[2].y=60;
MyRgn.CreatePolygonRgn(Points, 3, ALTERNATE);
利用以上的函数创建区域后,就可以调用CWnd::SetWindowRgn(HRGN hRgn, BOOL
bRedraw)来创建非矩形的窗口了。SetWindowRgn()函数参数说明:hRgn是一个CRgn类
的句柄;bRedraw如果被设置成TRUE,那么,在窗口次序发生变化时,系统会发
送WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED消息给窗口。
如果要创建外形更复杂的窗口,例如mp3播放器Soniq的一个播放界面,就是
两个圆形部分重合形成的。对于这类窗口的创建,还要用到CRgn类另外一个极其
重要的函数——CombineRgn()。首先要说明的是:在VC++5的在线帮助中,将这个
函数归入了初始化(Initialization)类型中,实际上,如果定义的CRgn类在没
有使用其它初始化函数初始化之前,就调用这个函数的话,程序将会失败,所
以,这个函数似乎应该归入operation类更恰当。
CombineRgn(CRgn* pRgn1, CRgn* pRgn2, int
nCombineMode)函数用来创建一个由多个多边形、椭圆合成的不规则区域。
pRgn1、pRgn2分别指向参与合成不规则区域的多边形或椭圆形;nCombineMode说
明合成的方式:RGN_AND最后的区域是pRgn1和pRgn2的重叠部分;RGN_DIFF最后
的区域是pRgn1中不包含pRgn2的部分;RGN_OR最后的区域同时包含pRgn1和
pRgn2;RGN_XOR最后的区域同时包含pRgn1和pRgn2,但不包含pRng1和pRng2重
叠的部分。例如,创建一个类似Soniq播放器的界面:
......
RECT m_Cyc1;
RECT m_Cyc2;
CRgn RgnCyc1;
CRgn RgnCyc2;
CRgn RgnDlg;
m_Cyc1.left=100; m_Cyc1.top=5; m_Cyc1.right=200; m_Cyc1.bottom=105;
m_Cyc2.left=80; m_Cyc2.top=85; m_Cyc2.right=180; m_Cyc2.bottom=185;
RgnDlg.CreateEllipticRgnIndirect( &m_Cyc1 );
RgnCyc1.CreateEllipticRgnIndirect( &m_Cyc1 );
RgnCyc2.CreateEllipticRgnIndirect( &m_Cyc2 );
RgnDlg.CombineRgn( &RgnCyc1, &RgnCyc2, RGN_OR );
MyWin.SetWindowRgn( (HRGN)RgnDlg, TURE );
......
作者:cvisual (贝贝)
对话框概述
一个通用对话框是由Windows设计和编写的,用于完成某一特定工作,比如打开
一个文件或选择一种颜色等。MFC提供了管理每一通用对话框类型的类,这些类概
括在下表中。表1 例如:处理File菜单上Open和Save As命令时使用CFileDialog
通用对话框,而处理Prin和Print Setup命令时则使用CPrintDialog通用对话框。
用这些对话框构造用户应用是很方便的,但有时这些通用对话框的外部特性和行
为不能满足用户要求,特别是在对话框上需要增加某些控件或删除某些控件时尤
为突出。本文提供给大家一种方法来定制通用对话框的外部特性和行为,可用自
己的代码与MFC提供的类协同处理这些对话框,以满足用户的不同要求。一方面
由于使用了MFC的通用对话框类,节省了大量的代码编程工作,提高了开发效率;
另一方面,采用自己的代码协同实现,使对话框的功能可以做到灵活多样。本文
以F ile Open命令为例,来阐述这一方法。
定制File Open通用对话框
CFileDialog类封装Windows公共文件对话框。公共文件对话框以一种与Windows
标准一致的方式,提供了实现File Open和File Save A s对话框(及其他文件选择
对话框)的简单方法。
CFileDialog中有一成员变量m_ofn,它是OPENFILENAME类型的结构。通常,要使用
一个F ileDialog对象,需要首先用CFileDialog的构造函数创建该对象。在对话
框被构造之后,且在用DoModal成员函数显示对话框之前,应用程序可以通过设置
m_ofn结构中的值,来对此对话框中的控件的值或状态进行初始化。例如:应用程
序可将m_ofn的lpsz Title成员设置为所想要的对话框标题;可将m_ofn的指定该
对话框的创建标志Flags成员设置为其各标志位值的多种组合,以达到一定要求
,其中,设置的Flags中的OFN—ALLOWMULTISELECT标志可允许用户选择多个文件;
设置OFN_HIDEREADONLY标志可隐藏Read Only检查框;设置O FN_OVERWRITEPROMT
标志可做到若所选择的文件已存在,则使Save As 对话框产生一个消息框,使用户
确认是否重写该文件;等等。有关此结构的详细说明,包括它的成员列表,参看
Windows SDK文档中OPENILE N AME的介绍。
从以上所述看,仅改变m_ofn的值只能做到简单的定制,若要对通用对话框的外观、
通用对话框已有控件进行进一步调整(功能改变或删除),以及给通用对话框增加新
的控制,则以上简单定制方法是做不到的。笔者将为大家提供一种方法,下面以一
例子来介绍。本例假定一应用程序要读入某一位图文件,要求对要读入的位图文
件进行预览,具体实现是在File Open通用对话框的基础上增加预显功能,即每当
选中一个位图文件时,就在对话框中显示此文件的位图。为节省编程工作量,可
对Windows提供的File Open通用对话框进行定制。本例定制对话框采取的方案
是直接使用CFileDialog类,设法获取标准通用对话框资源并对其进行修改,然后
编写File Open菜单命令处理程序和一个钩子函数(HOOK)来处理特殊要求。本例
在VC++4.0环境中创建一个DibPreView项目来实现。
1.获取并改造通用对话框资源
在使用VC++自动创建的应用程序中,系统菜单File项中的Open项的处理是调用了
系统由CommDll.DLL提供的标准File Open对话框,要实现对其定制就必须截获
Open项的命令,用自己的代码来处理,以替代系统提供的缺省处理。要做到这点并
不困难,只要用ClassWizard在合适的对象(比如文档类或视窗类等)中增加对
ID_FILE-OPEN控制的处理 ,即增加OnFileOpen函数,编写此函数就能实现对Open
命令的处理。在此函数中首先要创建一个CFileDialog类的对象,该对象的外观和
行为均同标准的File Open对话框相同,要改变该对象的外形或删除原对话框中的
某些控件或增加某些控件,都必须首先设法获取该对象所对应的标准资源,然后,
对其进行相应的修改。获取资源的方法有两种:
方法一:
在Msdev/Include目录中存有以上几个标准通用对话框的资源,以 FileOpen.Dlg、
FotD lg、FindText.Dlg、Color.Dlg和PrnSetup.Dl g文件方式存在,这些文件
其中的一些常量被定义在本目录中的dlgs.h 中。为了定制该对话框,就要以显式
方式引用这些资源,要将它们嵌入到本应用程序的资源中,然后再修改该对话框的
资源。具体步骤如下:
(1)用VC++4.0自动创建项目DibPreView的全部文件;
(2)以Text方式打开本项目中的资源文件DibPreView.rc,在此文件中的Dialog段
首行前加上语句:
#include
(3)将Afxres.h文件从Msdev/Mfc/Include目录中复制到本项目目录中,并打开新
复制的Afxres.h文件,将下面的语句添加到文件行首:
#include
方法二:
在VC++安装盘中的Msdev/Sample/Mfc/General/Clipart目录中存有资源文件
Commdlg.c ,此文件含有Color、File、Open(或Save As) 、Find、Font、Print、
Print Setup、Replce 几个通用对话框的资源,为了以显式方式引用这些资源,可
利用VC++的资源编辑器,将所需的通用对话框资源复制到本例项目资源文件中。
按以上两方法处理完后,在本项目的资源目录列表中的Dialog项中,出现一标识
用VC++6.0制作图片屏幕保护程序
VC++可谓神通广大,如果学到家了,或者就掌握了那么一点MFC,你也会
感到它的方便快捷,当然最重要的是功能强大。不是吗,从最基本的应用程
序.EXE到动态连接库DLL,再由风靡网上的ActiveX控件到Internet Server API,
当然,还有数据库应用程序……瞧,我都用它来做屏幕保护程序了。一般的屏幕
保护程序都是以SCR作为扩展名,并且要放在c:/windows 目录或
c:/windows/system 目录下,由Windows 98内部程序调用(Windows NT 是
在 c:/windows/system32 目录下)。怎么调用?不用说了,这谁不知道。
好了,我们来作一个简单的。选择MFC AppWizard(exe),Project Name 为
MyScreensaver,[NEXT],对话框,再后面随你了。打开菜单Project、
Settings,在Debug页、Executable for debug session项,以及Link页
中Output file name项改为c:/windows/MyScreensaver.scr,这样,你可以
调试完后,直接在VC中运行(Ctrl+F5),便可看到结果。当然,这样做的
唯一缺点是你必须手动清除Windows 目录下的垃圾文件(当然是在看到满意结
果后;还有,你可借助SafeClean 这个小东东来帮你清除,除非你的硬盘大的
让你感到无所谓……快快快回来,看我跑到那里去了)。接下来用Class Wizard
生成CMyWnd类,其基类为CWnd(在Base Class 中为generic CWnd)。这个类是我
们所要重点研究的。创建满屏窗口、计时器,隐藏鼠标,展示图片,响应键盘、
鼠标等等,这家伙全包了。至于MyScreensaverDlg.h与MyScreensaverDlg.cpp文
件我们暂时不管。打开MyScreensaver.cpp,修改InitInstance()函数:
BOOL CMyScreensaverApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
CMyWnd* pWnd = new CMyWnd;
pWnd->Create();
m_pMainWnd = pWnd;
return TRUE;
}
当然,再这之前得先 #include “MyWnd.h" 。后面要做的都在MyWnd.h
与 MyWnd.cpp 两文件中了。
下面给出CMyWnd 的说明:
class CMyWnd : public CWnd
{
public:
CMyWnd();
static LPCSTR lpszClassName; //注册类名
public:
BOOL Create();
public:
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyWnd)
protected:
virtual void PostNcDestroy();
//}}AFX_VIRTUAL
public:
virtual ~CMyWnd();
protected:
CPoint m_prePoint; //检测鼠标移动
void DrawBitmap(CDC& dc, int nIndexBit);
//{{AFX_MSG(CMyWnd)
afx_msg void OnPaint();
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnDestroy();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
MyWnd.cpp 文件:
……
CMyWnd::CMyWnd()
{
m_prePoint=CPoint(-1, -1);
}
LPCSTR CMyWnd::lpszClassName=NULL;
BOOL CMyWnd::Create()
{
if(lpszClassName==NULL)
{
lpszClassName=AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,
::LoadCursor(AfxGetResourceHandle(),MAKEINTRESOURCE(IDC_NOCURSOR)));
//注册类;IDC_NOCURSOR为新建光标的ID,这个光标没有任何图案
}
CRect rect(0, 0, ::GetSystemMetrics(SM_CXSCREEN),
::GetSystemMetrics(SM_CYSCREEN));
CreateEx(WS_EX_TOPMOST, lpszClassName, _T(“”), WS_VISIBLE|WS_POPUP,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
GetSafeHwnd(), NULL, NULL); //创建一个全屏窗口
SetTimer(ID_TIMER, 500, NULL);//计时器,ID_TIMER别忘了定义
return TRUE;
}
为了防止同时运行两个相同的程序,下面两个函数是必需的:
void CMyWnd::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
CWnd::OnAc
---- Visual C++ 是 一 种 功 能 十 分 强 大 的 程 序 设 计 语 言, 利
用 它 可 以 很 方 便。 快 捷 的 开 发 出Windows 下 的 各 种 应 用 程
序。 尤 其 是 其 内 置 了 数 据 库 的 接 口, 可 使 我 们 毫 不 费 力
地 操 作Accse,FoxBASE+ 等 许 多 数 据 库 文 件。 但 在 安 全 性 上,
由 于FoxBASE+ 是 一 种 早 期 的 产 品, 所 以, 不 能 像Accse 那 样
为 数 据 库 文 件 本 身 加 密, 这 就 可 以 让 入 侵 者 直 接 对 数
据 库 操 作 而 导 致 数 据 的 损 失。
---- 本 文 提 供 的 方 法 可 以 做 为 一 个 小 的 模 块 放 到 你 的 应
用 程 序 中 调 用, 实 现 数 据 库 的 加 解 密。
---- 首 先, 打 开Visual C++ 5.0 选 择New 建 立 一 个 基 于 对 话 框
的 应 用 程 序, 然 后 在 对 话 框 上 面 放 置 两 个 按 钮, 其 中 一
个 标 题 叫 做 加 密, 另 一 个 标 题 叫 做 解 密。 对 应 实 现 如 下:
void CAa6Dlg::OnButton1() (对应于加密按钮的单击)
{
int buf2[512],tcd,tcd1,*p1,i;
CFile cfile1;
char *pfilename;
pfilename="c://aa.dbf";
cfile1.Open(pfilename,CFile::modeReadWrite);
cfile1.Read(buf2,32);
p1=(int*)buf2;
p1=p1+1;
tcd=*p1;
tcd1=(tcd-1)/2;
cfile1.SeekToBegin();
cfile1.Read(buf2,tcd);
buf2[0]=buf2[0]+0x0017;
for (i=16;i
---- 这 样, 用 本 程 序 的 加 密 功 能 加 密 的Fox 系 列 数 据 库
( 如Foxbase+, Foxpro 等), 用Foxbase+,Foxpro 等 数 据 库 软 件 无
法 对 其 进 行 读 写。 只 有 经 过 解 密 之 后, 才 能 被 其 识 别。
---- 比 较 复 杂 的 数 据 库 中 一 般 会 有 位 图 数 据( 比 如 相 片)
。 虽 然 这 类“OLE 对 象” 的 插 入、 删 除 以 及 替 换 操
作 在ACCESS 里 容 易 实 现, 在VC 中 却 显 得 复 杂 而 且 颇 费 周 折。
以 下 把 作 者 用VC 处 理ACCESS 数 据 库 中 的 位 图 数 据 的 体 会
简 单 叙 述 一 下, 以 请 教 于 大 家。
---- 在 CdaoRecordset 派 生 类 的 对 象 中,VC 自 动 为ACCESS 的
“OLE 对 象” 域 生 成 一 个CLongBinary 对 象。 该 类 虽
然 较 简 单, 在 程 序 里 却 需 要 使 用 全 局 函 数GlobalAlloc() 和
GlobalFree() 处 理 与 它 的 内 存 句 柄m_hData 有 关 操 作, 访 问 数
据 前 后 要 调 用GlobalLock() 和GlobalUnlock(), 而 且 还 要 给 它
的m_dwDataLength 赋 值, 使 用 起 来 相 对 复 杂, 所 以 一 般 推 荐
使 用CByteArray 类。 这 只 需 要 在CdaoRecordset 派 生 类 对 象 的 数
据 说 明 里 修 改 一 下, 并 把DoFieldExchange() 里 的
DFX_LongBinary() 改 成DFX_Binary() 即 可。
---- 作 者 定 义 了 一 个 以CObject 为 基 类 的CDib 类
(CDaoRecordView 的 派 生 类 里 定 义 了CDib 对 象 成 员m_DIB), 其
中 包 括 一 下 成 员 和 方 法:
CByteArray m_bufDIB;
BOOL Create(CByteArray& ba);
BOOL Create(CFile& bmpFile);
BOOL Paint(HDC hDC);
---- m_bufDIB 是 存 储 位 图 数 据 的 缓 冲 区。 为 简 便 起 见, 它
不 包 含 包 装 信 息 和BITMAPFILEHEADER 结 构。 这 样 对 数 据 库 更
新 后, 原 有 的“OLE 对 象” 类 型 将 变 成“ 长 二
进 制 数 据”, 不 能 在ACCESS 里 查 看 了。
---- 第 一 个Create() 重 载 方 法 的 参 数ba 是 记 录 集 的 位 图 数
据( 比 如m_image), 使 用CByteArray::Copy() 把 数 据 复 制 给
m_bufDIB; 第 二 个Create() 方 法 的 参 数bmpFile 是 已 打 开 的 位
图 文 件, 使 用CFile::ReadHuge() 把 文 件 里 的 数 据 读 入m_bufDIB
( 放 弃 前 面 的BITMAPFILEHEADER 结 构):
DWORD dwBufSize;
dwBufSize = bmpFile.GetLength();// 获 得 文 件 长 度
bmpFile.Seek((long)sizeof(BITMAPFILEHEADER),
CFile::begin);// 放 弃 文 件 头
dwBufSize-=sizeof(BITMAPFILEHEADER);
m_bufDIB.SetSize(dwBufSize );// 设 置 缓 冲 区 大 小
file.ReadHuge((LPSTR)(m_bufDIB.GetData()), dwBufSize);
……
---- Paint() 方 法 调 用 了SetDIBitsToDevice() 函 数( 根 据 情 况 也
可 以 使 用StretchDIBits ()), 参 数hDC 是CDaoRecordview 的 资 源 中
的 一 个 静 态 控 制 的 设 备 句 柄, 作 为SetDIBitsToDevice() 的 第
一 个 参 数。 如 果 不 是16 或24 位 的 位 图, 还 需 要 建 立 和 设 置
调 色 板。Paint() 方 法 除 了 在 CDaoRecordView 派 生 类 的OnMove() 里
调 用 外, 也 被OnPaint() 调 用( 最 好 不 在OnDraw() 里 调 用):
void CDerivedView::OnPaint()
{
CPaintDC dc(this);
CClientDC dc1(&m_ctlImage);
if(m_DIB.Create(m_pSet->m_image))
m_DIB.Paint(dc1.m_hDC);
}
---- 作 者 首 先 采 用 的 方 法 是, 每 当 打 开 一 个 位 图 文 件,
调 用m_DIB.Create() 和m_DIB.Paint(), 然 后 复 制 给m_pSet->m_image,
再 设 置“ 脏” 标 识:
if(m_DIB.Create(bmpFile))
{
CClientDC dc(&m_ctlImage);
m_DIB.Paint(dc.m_hDC);
(m_pSet->m_image).Copy(m_DIB.m_bufDIB);
SetFieldDirty(&(m_pSet->m_image));
}
---- 记 录 滚 动 时,OnMove() 调 用Update() 对 数 据 进 行 更 新。
---- 但 是 这 样 做 的 结 果 是, 只 有 在 域 的 内 容 不 为 空(NULL)
的 时 候 才 能 更 新 数 据。 也 就 是 说, 添 加“ 长 二 进 制
数 据” 不 能 实 现。
---- 最 后 发 现 使 用SeieldValue() 可 以 实 现 添 加 和 替 换。 但
由 于 作 者 未 知 的 原 因, 还 需 要 把 另 外 某 个 域 设 置 为
“ 脏” 才 行:
if(m_DIB.Create(bmpFile))
{
CClientDC dc(&m_ctlImage);
m_DIB.Paint(dc.m_hDC);
(m_pSet->m_image).Copy(m_DIB.m_bufDIB);
// 只 为OnPaint() 调 用 时 使 用
m_pSet->SetFieldValue(_T("[image]"),
COleVariant(m_DIB.m_bufDIB));
m_pSet->SetFieldDirty(&(m_pSet->m_name));
// 任 意 另 外 一 个 域
}
---- 如 果 打 算 删 除 数 据 库 里 的 位 图 数 据, 可 以 把 一
个“ 空” 的CByteArray 对 象 替 换 原 来 的 就 行 了。
问:我正在试着用MFC来制作弹出窗口,我看过一些关于建立弹出窗口的文章,
它们是使用 CWnd对象的。但在文档,视窗结构中是怎样实现的?
答:你可以建立一个非模态对话框(使用Create函数),你可以在任何建立窗
口,子窗口等。 如果你一定要在文档、视窗结构中实现,你也可以用
CCreateContest类。 下面是建立MDI窗口的例子:
{
LPCTSTR lpszClassName = NULL;
CCreateContext cContext;
cContext.m_pNewViewClass = RUNTIME_CLASS ( CMyView )
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW &
~WS_THICKFRAME & ~WS_MAXIMIZEBOX;
// TODO: Add your specialized code here and/or call the base class
if ( CMDIChildWnd::Create(lpszClassName, lpszWindowName, dwStyle,
pParentWnd->rectDefault, pParentWnd, &cContext) )
{
InitialUpdateFrame ( NULL, TRUE );
CScrollView *pView = ( CScrollView* ) GetActiveView();
if ( pView )
pView->ResizeParentToFit ( FALSE );
return TRUE;
}
else
return FALSE;
}
CCreateContext有一个成员为m_pCurrentDoc,你可以用它来将一个文档分配到相应的窗口上.
现代显示器的很多优点,如长寿命的显示屏,液晶和能源之星能源保护模式, 已
经让屏幕保护程序的作用大大地降低. 但是,依然有大量的屏幕保护程序出现,
尤其是共享软件.
这也许是因为写屏幕保护程序是一件非常有趣的事. 因为有CRect和CGdiObject
类,这些类的绘图功能比单纯的C API函数容易得多, 所以,用MFC写屏幕保护程
序会是一件更有趣的事.
简单地和"Hello, world."应用程序比较,它不需要WinMain()函数,例如:
如果你发掘一下有哪些API函数支持产生一个屏幕保护程序,你会发现可利用的
函数非常少. 例如:用C写屏幕保护程序,大多数情况下都不需要调用
DefWindowProc函数,取而代之的是DefScreenSaverProc函数. 如果调用你自已
的函数(一般也就是三个),你可以编出一个和标准的屏幕保护程序功能一样的屏
保程序.
对所有的屏幕保护程序:
屏幕保护程序的名字和描述在字符串1中定义.
屏幕保护程序的图标为ID_APP, 在中定义为100.
WINAPI函数ScreenSaverProc必须定义和调用.
(CScreenSaverWnd模块为你填充这个API调用.)
程序必需以.SCR为扩展名.
对于可设置的屏幕保护程序:
设置屏幕保护程序的对话框为DLG_SCRNSAVECONFIGURE,在定义为2003.
WINAPI函数ScreenSaverConfigureDialog必须被定义和调用.
WINAPI函数RegisterDialogClasses必须被定义和调用.
(CScreenSaverDlg模块为你填充这个API调用.)
---------------------------------------------------------------------
---
某些MFC外部特性
所有这些简单的特性来源于MFC,以及它便利的应用程序框架. 一个屏幕保护程
序并不真的是一个应用程序,它只不过是操作系统在你离开键盘后才调用的一段
代码. 它甚至不需要WINMAIN函数,MFC程序似乎不可能完成这一点,因为它已经
调用了WINMAIN函数. 如果你没有用过WINAPI来编写C程序,你可能不知道MFC已
经在幕后调用了DefWindowProc过程.
你当然能用MFC来生成一个屏幕保护程序,对于上述的限制,你只要在MFC的基础
上做一点点工作即可. 以下提供两个抽象类 CScreenSaverWnd和
CScreenSaverDlg,它会考虑这种限制,并且让你很容易创建一个功能完全的屏幕
保护程序.
建一个基于对话框的MFC应用程序.
使用VC4.2或5.0为屏幕保护程序产生一个新工程,你可以使用开发环境提供的
AppWizard, 建立一个新"Win32 MFC Application"程序.如果你选择链接时MFC
为共享(linked with MFC in a shared DLL), 屏幕保护程序会小很多.当然,基
于对话框的应用程序将会避免产生不需要用DOC/VIEW构架.
删除所以关于CWinApp的引用和它本身.
删除所有CWinApp派生类的申明和定义,包括一个全局的instance.
产生一个CWnd的派生类.
我们已经创建了一个基于对话框的应用程序,但是屏幕保护程序只是需要一个简
单的CWnd派生类. 你可以使用ClassWizard来产生一个继承于 generic CWnd
class的派生类.
选择父类.
从下载的文件中拷贝CScreenSaverWnd和CScreenSaverDlg的相关文件,*.CPP和
*.H (作者要求你能保留源代码中的版权信息). 在你的窗口类中查找CWnd,将其
换成CScreenSaverWnd,将CDialog换为CScreenSaveDlg. 然后重新编译.
一个特定的对话框.
用ClassWizard产生的CDialog的派生类,没有处理命令行参数的构造函数. 因为
屏幕保护程序的设置部分是一个窗口,需要命令行设置, 因此,在此提供了一个
可以使用命令行的构造和析构函数.
全局考虑.
当删掉CWinApp的派生类对象时,也同时删掉了全局的instance, 因此,程序中
CScreenSaverWnd的派生类需要有一个全局的instance. 同样,在
CScreenSaverDlg的派生类中也要保留一个副本.
资源.
如前所述,屏幕保护程序包含以下资源: 字符串1中的描述,不要超过20个字符,
当用户选择屏幕保护程序时,在下拉框中就会出现这个字符串.将图标资源的ID
改为100.将对话框资源的ID改为2003.
泡沫,清洗,重复.
你已经做好一个框架,现在可以编译,调试和开发了. 你可以改变工程输出的文
件扩展名为.SCR,从而能出现屏幕保护程序的设置对话框. 如果你想调试屏幕保
护程序,在运行时你可以用命令行参数:"/save"
分析自带的示例.
示例使用VC5.0,但应该兼容于VC4.2,展示了 CScreenSaverWnd和
CScreenSaverDlg的用法, 并且使用了CImageList来调用一个图标库,在屏幕上
产生动画,请查看源程序的注解.
---------------------------------------------------------------------
---
代码
虽然CScreenSaverWnd不是CView的派生类,我觉得应该重载OnInitialUpdate和
OnDraw.我也加入了三个特性,你可以使用,也可以不使用.
CScreenSaverWnd的默认状态是黑屏,这由函数OnEraseBkgnd()来完成,你可以在
构造函数,OnCreate,OnInitalUpadte这三个地方的任一处调用
SetAutoBlack(FALSE)来关掉该项.
成员变量m_pPalette指向CPalette,将被用于OnDraw调用之前的调色板设置, 重
载OnQueryNewPalette()和OnPaletteChanged(
现在有不少的软件都有这样的一种界面效果:当用户单击某一个按钮之后,并不
是简单地执行某种功能或弹出一个对话框,而是在按钮旁边弹出一个菜单,让用
户作更详细地选择,这在某种程度上就代替了简单的对话框,而且较对话框更
为"用户友好"。这样的按钮基本上有两种类型:在按钮上显示文字的和在按钮
上显示箭头的,显示箭头常见的有向右的和向下的两种,还有向上的和向左的。
图示为常见的风格,即向下的箭头和在按钮左下角弹出菜单。那么,我们在编
程时如何实现这一功能呢?
---- 我们知道,MFC中的CButton类有一个虚函数名叫DrawItem(),若在对话框
模板中为控件指定了BS_OWNERDRAW风格,则在运行时将调用这个函数来画按钮,
而CMenu类的成员函数TrackPopupMenu()则可以在屏幕的任何位置弹出菜单。由
上得到启发,只要我们合理地使用这两个函数,就能创建出"菜单按钮"来。
---- 下面的CMenuButton类封装了全部的这些功能,让我们先来看一下它的制作
原理。
---- 在取得了按钮的矩形区域之后,取其一个角落的值传递给
TrackPopupMenu()函数即可实现弹出菜单,在TrackPopupMenu内部使用
TPM_RETURNCMD标志可以得到用户选择的菜单的命令ID,以供进一步的处理;
在重载了DrawItem()函数之后,我们可以在函数的内部使用
CDC::DrawFrameControl()函数来画出基本的按钮外观,再在中间部位画一个箭
头即可。箭头可以用Marlett字体来画。也许有人会担心,若果其他人的机器没
装Marlett字体怎么办?其实,任何一台安装Windows的机器离开了Marlett字体
都无法正常工作,先请看下图,这是Windows"系统工具"中自带的"字符映射表"。
---- 看到最上面一行中的那几个箭头了吗?就是要把它们画在按钮上。等一等,
另外的几个符号怎么也那么熟悉?这不就是几乎每个窗口上都有的"最小化"、
"还原"、"关闭"和"最大化"按钮吗?不错,Windows正是使用这几个字符在标题
栏上绘图的。其实,Windows中的最"标准"的画箭头的方法就是使用Marlett字
体,无论是工具栏上的箭头还是组合框中的箭头,都是这样画出的。有时,在乱
删了字体之后,组合框或工具栏的下拉箭头会变成数字6或者9,为什么?看到
状态栏上的"击键值"了吗?--"6",往右数,那个小一点的下箭头正好是--"9"。
---- 下面是具体的制作过程。
---- 首先,生成一个MFC AppWizard EXE 工程,最好是基于对话框的工程,当
然,利用现有的工程也可以。生成一个以CButton为基类的新类,名为
CMenuButton,然后用ClassWizard为其添加两个成员函数:DrawItem()
和PreSubclassWidnow();手工为CMenuButton类添加BOOL类型m_bDrawFocusRect
成员变量,用于决定是否在按钮上画焦点矩形,添加SetDrawFocusRect()函数用
于设置这个标志,默认为画焦点矩形;添加两个枚举类型的变量m_ArrowType和
m_PopupPos,用于决定所画的箭头的类型和菜单弹出的位置。箭头可为右箭头、
下箭头、小右箭头、小下箭头、上箭头和左箭头(参见本文开始处的图);菜单
的弹出位置可以为按钮的左上角、右上角、左下角和右下角。最后手工添加两
个函数,SetArrowType()和SetMenuPopupPos(),用于设置以上各种风格,其默
值分别为画右箭头和在左下角弹出。如果只需要菜单而不需要画箭头,只需置空
BS_OWNERDRAW标志位即可,添加一个SetStyle()函数,用于设置是画箭头还是显
示文本,其默认值是画箭头。 为方便处理按钮的BN_CLICKED通知消息,为
CMenuButton类创建一个公有的成员函数OnClick(),以便在BN_CLICKED的消息处
理器中调用。它有两个参数,第一个是菜单资源的ID,第二个参数为子菜单的
ID,默认为0。如果只有一组子菜单,则可使用其默认值0。OnClick()函数的
返回值为所选的菜单项的命令ID,若未作任何有效选择,则返回0。
黄 晓 润
---- 在 数 据 库 应 用 时, 有 时 会 遇 到 这 样 的 情 况: 记 录 的 某 项
信 息 由 变 长 数 组 构 成。 这 时 传 统 方 法 是 建 立 一 个 新 表, 并 通
过 表 间 的 关 系 来 记 录 将 相 应 数 据 关 联 起 来; 但 是 这 样 实 现 相
对 比 较 复 杂, 而 且 也 不 符 合 思 维 逻 辑。 由 于 该 数 组 整 个 构 成
记 录 的 一 个 具 体 项, 因 此 如 果 能 够 将 它 作 为 一 个 字 段 则 更 加
直 观 些, 同 时 在 一 些 情 况 下 也 比 较 容 易 处 理。 那 么, 下 面 将
向 您 提 供 一 种 这 样 的 方 法。 为 了 说 明 上 的 方 便, 我 首 先 假 定
了 下 面 的 需 求:
---- 在 个 人 简 历 数 据 表 中, 需 要 将 工 作 经 历 分 为 起、 止 时 间、
单 位、 职 位 等 信 息。
---- 在 这 种 情 况, 可 以 分 别 建 立 个 人 简 历 和 工 作 经 历 两 个 表
并 将 之 通 过 关 键 字 进 行 关 联, 从 而 达 到 要 求。 但 也 可 以 采 用
只 建 立 一 个 表 的 方 式 来 完 成 数 据 的 管 理,
---- 那 么 如 何 完 成 呢 ? 下 面 将 给 出 具 体 的 实 现。
---- 首 先 建 立 一 个 表Person, 记 录 包 含 个 人 简 历 需 要 的 各 个 字
段, 将 工 作 经 历 作 为 一 个 字 段, 其 类 型 为Image( 在Access 数 据 库
中 为"OLE 对 象")。 下 面 新 建 类CPersonRs。 将 它 与 表Person 对 应 起
来, 并 将 m_dResume 对 应 到 字 段 工 作 经 历 中,m_dResume 的 类 型 为
CByteArray 。 下 面 我 们 来 定 义 工 作 定 义 的 基 本 结 构:
class CResumeItem
{
public:
COleDateTime m_timeBegin;
COleDateTime m_timeEnd;
COleDateTime m_strCompany;
COleDateTime m_strTitle;
Void Serialize( CArchive& ar )
};
typedef CArray< CResumeItem, CResumeItem& > CResume;
---- 在CPersonRs 中 定 义 下 面 的 函 数
CPersonRs
{
……
CByteArray m_dResume;
……
void SetResume( CString strName, CResume& resume );
void GetResume( CString strName, CResume& resume );
};
并在相应的文件给出相应的函数的实现;
void CResumeItem::Serialize( CArchive& ar )
{
if ( ar.IsLoading() )
{
ar > > m_timeBegin
> > m_timeEnd
> > m_strCompany
> > m_strTitle;
}
else
{
ar < < m_timeBegin
< < m_timeEnd
< < m_strCompany
< < m_strTitle;
}
}
函数SetResume和GetResume实现如下:
void CPersonRs::SetResume
( CString strName, CResume& resume )
{
…………//根据strName定位到相应的记录
Edit();
CMemFile memFile;
CArchive ar( &memFile, CArchive::store );
Resume.Serialize( ar );
ar.Close();
DWORD dwSize = memFile.GetLength();
LPBYTE lpInfo = memFile.Detach( );
m_dResume.SetSize( dwSize );
memcpy( m_dResume.GetData(),lpInfo,dwSize);
SetFieldNull( &m_dResume, FALSE );
SetFieldDirty( &m_dResume );
ASSERT( CanUpdate() );
Update( );
free( lpInfo );
}
void CPersonRs::GetResume
( CString strName, CResume& resume )
{
…………//根据strName定位到相应的记录
LPBYTE lpInfo;
DWORD dwSize;
dwSize = m_dResume.GetSize();
lpInfo = m_dResume.GetData();
memFile.Attach( lpInfo, dwSize );
CArchive ar( &memFile,CArchive::load );
resume.Serialize( ar );
ar.Close();
memFile.Detach( );
}
---- 通 过SetResume 和GetResume 可 以 方 便 地 将Resume 的 内 容 读 出 或 写
入 到 记 录 的 工 作 经 历 字 段 中, 而 且 更 新 也 相 当 方 便。 这 样 是
就 可 以 在 工 作 经 历 字 段 任 意 个 工 作 经 历 项, 而 且 由 于 是 在 一
个 表, 也 省 去 了 多 表 造 成 的 麻 烦。
---- 不 过, 最 后 需 要 声 明 的 是, 上 述 例 子 的 情 况 根 据 实 际 需
要 可 能 用 多 表, 但 这 里 只 是 借 它 说 明 这 样 一 种 实 现 方 法, 也
许 你 会 在 某 些 情 况 下 这 些 会 相 当 简 单 些。 本 文 也 只 给 出 实 现
的 关 键 代 码, 对 于 如 何 使 用MFC 访 问 数 据 库、 序 列 化 机 制 可 以
参 见VC 的 相 应 文 档。
齐玉东 李逸波
传统数据库系统缺乏知识,只能处理静态数据;而专家系统的狭窄
应用领域及不能访问现存数据库,又防碍了专家系统的有效应用。数
据库和人工智能这两个领域单独发展的局限性,促使了两者取长补短,
共同发展。这就是专家数据库EDS(Expert Database Syst em)产生和
发展的原因。通常,我们把既具有数据库管理功能及演绎能力、又提
供专家系统中若干良好性能的数据库系统,称为专家数据库。EDS的基
本思想是把以知识表达和知识处理为主的专家系统ES(Expert System
)技术引进传统数据库,使二者有机结合,以开发出能共享信息的面向
知识处理的问题求解系统。目前,EDS主要采用系统耦合--"紧耦合"
及"松耦合"来实现。紧耦合指将规则管理系统集成到DBMS之中,使DBM
S既管理数据库又管理规则库。这种方法实现难度较大。而松耦合是
指将一个现成的专家系统外壳和一个现成的DBMS作为两个独立的子系
统结合在一起,它们分别管理规则库和数据库。采取松耦合实现策略
可以充分发挥原有两个系统的全部功能,而不需对原系统进行任何改
动。它只需设计一个连接ES/DBMS的高效、灵活的接口模块,以协调二
者的工作,所以实现起来时间短、见效快。
一、故障诊断专家系统的系统结构
在故障诊断系统HF-2000的研制中,我们采取松耦合策略建立了一
个故障诊断专家数据库系统。该系统是一个产生式系统,采用深度优
先策略作为其控制策略。系统根植于W indows平台,采用了面向对象
的程序设计技术及先进的数据库技术;在数据库端,我们采用了基于Se
rver/Client机制的MS SQL的数据库技术。在推理控制端,利用Visual
C++进行编程,实现了一个推理机。推理机与数据库之间的接口则通
过ODBC API直接调用来实现对数据库的访问。本系统的构造模型是以
数据库为载体的构模形式,系统机构图如1所示。图1
图1中知识获取结构负责建立、修改与扩充各个数据库;解释机构
用于对求解过程作
出说明,指出求解成功或失败的原因,并回答用户提出的问题。事
实库用来存放输入的原始事实及中间结果;字典库用来存放规则中事
实的基本定义和说明;规则库用来存放规则;垃圾桶用来存放推理中失
败的推理路径。
二、规则与数据库的设计
1. 产生式规则的模型
规则的一般形式是:
if〈前提〉then〈结论〉
它表示当〈前提〉成立时,得出〈结论〉的可信度为。其中〈前
提〉是事实或断言的合取形式。本系统中的规则模型请参考图2。
2. 事实库
结构:FACT_DB(Fact_ID,Rank,No)
用途:存放输入的原始事实,中间结果及最后结果。
其中:Fact_ID是事实Fact的编码;Rank用来表示系统特定部分,比
如说"放大级"、"槽路"等;No表示特定部分中的部件的编号;如"1"表
示"槽路"部分1号管、"2"表示"槽路"部分2号管等。
3. 字典库
结构:DICT_DB(Fac_ID,Component,Appear,Why,Known)
用途:存放规则库中的前提条件和结论及其编码。
其中:Fact_ID为事实编码;Component为部件名称;Appear是对Fac
t_ID的自然语言解释;Known用来表示该事实已知或未知,以防止该断
言的重复求证。
4. 规则库
规则库中包括四个表(TABLE),它们是规则前件库(PRE_TABLE)、
已激活的规则前件库(ACTIVE_PRE TABLE)、规则后件库(ACT_TABLE)
和已激活的规则后件库(ACTIVE_ACT_TAB LE)。
图2
(1)规则前件库
结构:PRE_TABLE (Rule_Name, Fact_ID)
用途:存放各条规则对应的前提条件。
其中:Rule_Name为规则名;Fact_ID为Rule_Name规则的一个与条
件;一条规则的n个与条件在该库中就有n条对应该规则的记录。
(2)已激活的规则前件库
结构:ACTIVE_PRE_TABLE(Fact_ID,Rank,No)
用途:存放已激活的前提条件,以避免规则各前提条件的重复匹配
。
其中:Fact_ID为Rule_Name规则的一个与条件;Rank用来表示系统
特定部分;No表示特定部分中的部件的编号。
(3)规则后件库
结构:ACT_TABLE (Rule_Name, Fact_ID, Num, Num2)
用途:存放规则对应的结果。
其中:Rule_Name为规则名;Fact_ID为Rule_Name规则的结果;Num
表示该规则前提条件的个数;Num2为Num字段的辅助值。
(4)已激活的规则后件库
结构:ACTIVE_ACT_TABLE(Rule_Name, Rank,No)
用途:存放已激活的后件,以避免规则各结论的重复匹配。
其中:Rule_Name为规则名;Rank用来表示系统特定部分;No表示特
定部分中的部件的编号。
5. 垃圾桶
结构:GARBAGE_BIN_DB(Fact_ID, Rule_Name, Pre_Num)
用途:记录剪去枯死枝叶的原因。
其中:Fact_ID为事实编码;Rule_Name为应用于该结点的规则名,P
re_Num为实际匹配的前提条件数。
三、控制机构的设计
我们用C++语言实现了一个采用深度优先策略的反向推理机。整
个推理过程。就是一棵搜索树边长枝边修枝的过程推理机的源程序如
下:
int CCause::Reason(RTree*rTree)
{
RULE prule,rule;
问:我想创建这样一个校验框类,它的自动特性只出现在用户点击校验标记时,
而不是当文本 被点击时.在点击文本时,我只想设置焦点,就象CheckListBox那样.
答:在按钮类的派生类中,例如CCheckBox,处理WM_LBUTTONDOWN消息:
void CCheckBox::OnLButtonDown( UINT nFlags, Cpoint point )
{
// if the checkmark was clicked, pass the message onto the button
if ( HitTest( point ) )
Cbutton::OnLButtonDown( nFlags, point );
else // otherwise set the focus only
SetFocus();
}
BOOL CCheckBox::HitTest( Cpoint& point )
{
Crect rcClient;
GetClientRect( rcClient );
// Checkmark rect
Crect rc;
// Checkmark square size, hard coded here because I couldn’t find any method to obtain this value from
the system.
// Maybe someone else knows how to do it?
Const int nCheckSize = 13;
DWORD dwStyle = GetStyle();
if ( ( dwStyle & BS_VCENTER ) == BS_VCENTER )
rc.top = rcClient.top + ( ( rcClient.bottom - rcClient.top ) -nCheckSize ) / 2;
else if ( dwStyle & BS_TOP )
rc.top = rcClient.top + 1;
else if ( dwStyle & BS_BOTTOM )
rc.top = rcClient.bottom - nCheckSize - 2;
else // Default
rc.top = rcClient.top + ( ( rcClient.bottom - rcClient.top ) -nCheckSize ) / 2;
if ( dwStyle & BS_LEFTTEXT )
rc.left = rcClient.right - nCheckSize;
else
rc.left = rcClient.left;
rc.right = rc.left + nCheckSize;
rc.bottom = rc.top + nCheckSize;
return rc.PtInRect( point );
}
问:我编了一个小巧而有趣的工具,当用户使用时我不想让它显示出任何用户
界面。听听各位有办法可将视关闭。
答:你可以注册一个新的窗口类型,它拥有除了WS_VISBLE属性外的任何属性,
类似CFrameWnd,在PreCreateWindow方法中实现。
另外,你能在OnCreate方法中通过设置m_nCmdShow为SW_HIDE来实现,
具体方法如下:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// hide our app
AfxGetApp()->m_nCmdShow = SW_HIDE;
return 0;
}
andymei 收藏
摘 要: 四通利方(RichWin),中文之星(CStar)是大家广为熟知的汉化Windows产
品,“陷阱”技术即动态修改Windows代码,一直是其对外宣称的过人技术,它
究竟是如何实现的,这自然是核心机密。本文试图解开这个秘密,并同时介
入Windows的模块调用机制与重定位概念,并给出了采用"陷阱"技术动态修改
Windows代码的示例源程
序。
一、发现了什么?
作者多年来一直从事Windows下的软件开发工作,经历 了Windows2.0、3.0、
3.1,直至WindowsNT,95的成长过程,也遍历了长青窗口、长城窗口、DBWin、
CStar、RichWin等多个Windows汉化产品。从现在看来,影响最大也最为成功的,
当推四通利方的RichWin,此外,中文之星CStar与RichWin师出一门,其核心技
术自然也差不许多。其对外宣传采用独特的“陷阱”技术动态修改Windows代码,
一直是作者感兴趣的地方。
EXEHDR是MicrosoftVisualC++开发工具中很有用的一个程序,它可以检查
NE(New_Executable)格式文件,用它来分析RichWin的WSENGINE.DLL或CStar
的CHINESE.DLL就会发现与众不同的两点:
( 以CStar 1.20 为 例)
C:/CSTAR>exehdr chinese.dll /v
..................................
6 type offset target
BASE060aseg 2 offset 0000
PTR 047eimp GDI.GETCHARABCWIDTHS
PTR 059bimp GDI.ENUMFONTFAMILIES
PTR 0451imp DISPLAY.14( EXTTEXTOUT )
PTR 0415imp KEYBOARD.4( TOASCII )
PTR 04baimp KEYBOARD.5( ANSITOOEM )
PTR 04c9imp KEYBOARD.6( OEMTOANSI )
PTR 04d8imp KEYBOARD.134( ANSITOOEMBUFF)
PTR 05f5imp USER.430( LSTRCMP )
PTR 04e7imp KEYBOARD.135( OEMTOANSIBUFF)
PTR 0514imp USER.431( ANSIUPPER)
PTR 0523imp USER.432( ANSILOWER )
PTR 05aaimp GDI.56( CREATEFONT)
PTR 056eimp USER.433( ISCHARALPHA )
PTR 05b9imp GDI.57( CREATEFONTINDIRECT )
PTR 057dimp USER.434( ISCHARALPHANUMERIC )
PTR 049cimp USER.179( GETSYSTEMMETRICS )
PTR 0550imp USER.435( ISCHARUPPER)
PTR 055fimp USER.436( ISCHARLOWER)
PTR 0532imp USER.437( ANSIUPPERBUFF)
PTR 0541imp USER.438( ANSILOWERBUFF)
PTR 05c8imp GDI.69( DELETEOBJECT )
PTR 058cimp GDI.70( ENUMFONTS )
PTR 04abimp KERNEL.ISDBCSLEADBYTE
PTR 05d7imp GDI.82( GETOBJECT)
PTR 048dimp KERNEL.74 ( OPENFILE )
PTR 0460imp GDI.91( GETTEXTEXTENT)
PTR 05e6imp GDI.92( GETTEXTFACE)
PTR 046fimp GDI.350 ( GETCHARWIDTH )
PTR 0442imp GDI.351 ( EXTTEXTOUT )
PTR 0604imp USER.471( LSTRCMPI )
PTR 04f6imp USER.472( ANSINEXT )
PTR 0505imp USER.473( ANSIPREV )
PTR 0424imp USER.108( GETMESSAGE )
PTR 0433imp USER.109( PEEKMESSAGE)
35 relocations
*******扩号内为作者加上的对应WindowsAPI函数
第一,在数据段中,发现了重定位信息。
第二,这些重定位信息提示的函数,全都与文字显示
输出和键盘,字符串有关。也就是说汉化Windows,必须修改这些函数。
在这非常特殊的地方,隐藏着什么呢?无庸致疑,这与众不同的两点,对打开
“陷阱”技术之门而言,不是金钥匙,也是敲门砖。
二、Windows的模块调用机制与重定位概念
为了深入探究“陷阱”技术,我们先来介绍Windows的模块调用机制。Windows的
运行分实模式(RealMode),标准模式(StandMode)和增强模式
(386EnhancedMode)三种,虽然这几种模式各不相同,但其核心模块的调用
关系却是完全一致的。
主要的三个模块,有如下的关系:
KERNEL是Windows系统内核,它不依赖其它模块。
GDI是Windows图形设备接口模块,它依赖于KERNEL模块。
USER是Windows用户接口服务模块,它依赖于KERNEL,GDI模块及设备驱动程序
等所有模块。
这三个模块,实际上就是Windows的三个动态连接库,在系统的存在形式如下,
KERNEL有三种不同形式,Kernel.exe(实模式),Krnl286.exe(标准模式),Krnl386.
exe(386增强模式);GDI模块是Gdi.exe;USER模块是User.exe,虽然文件名都
以EXE为扩展名,但它们实际都是动态连接库。
同时,几乎所有的API函数都隐藏在这三个模块中。用EXEHDR对这三个模块分
析,就可列出一大堆你所熟悉的WindowsAPI函数。
以GDI模块为例,
C:/WINDOWS/SYSTEM>exehdr gdi.exe
Exports:
ord seg offset name
............
351 1923eEXTTEXTOUT exported, shared data
56 319e1CREATEFONT exported, shared data
............
至此,你已能从Windows纷繁复杂的系统中,理出一些头续来。下面,再引入一
个重要概念——重定位。
一个Windows执行程序对调用API函数,或对其它动态库的调用,在程序装入内
存前,都是一些不能定位的动态连接,当程序调入内存时,这些远调用都需要
重新定位,重新定位的依据就是重定位表。在Windows执行程序(包括动态库)
的每个段后面,通常都跟有这
作者: Oleg Galkin.
MFC静态分割窗口有很多局限性。它们不能动态的显示或隐藏窗格。为了解决这
个问题,我重载了CSplitterWnd。新的代码将支持这一功能。
///////////////////////////////////////////////////////////////////
/
// splitex.h
// (c) 1997, Oleg G. Galkin
class CSplitterWndEx : public CSplitterWnd
{
protected:
int m_nHidedCol; // hided column number, -1 if all columns
// are shown
public:
CSplitterWndEx();
void ShowColumn();
void HideColumn(int colHide);
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSplitterWndEx)
//}}AFX_VIRTUAL
// Generated message map functions
protected:
//{{AFX_MSG(CSplitterWndEx)
// NOTE - the ClassWizard will add and remove
//member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////
/
// splitex.cpp
// (c) 1997, Oleg G. Galkin
#include "stdafx.h"
#include "splitex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////////////////////////////////
/
// CSplitterWndEx
CSplitterWndEx::CSplitterWndEx() :
m_nHidedCol(-1)
{
}
void CSplitterWndEx::ShowColumn()
{
ASSERT_VALID(this);
ASSERT(m_nCols < m_nMaxCols);
ASSERT(m_nHidedCol != -1);
int colNew = m_nHidedCol;
m_nHidedCol = -1;
int cxNew = m_pColInfo[m_nCols].nCurSize;
m_nCols++; // add a column
ASSERT(m_nCols == m_nMaxCols);
// fill the hided column
int col;
for (int row = 0; row < m_nRows; row++)
{
CWnd* pPaneShow = GetDlgItem(
AFX_IDW_PANE_FIRST + row * 16 + m_nCols);
ASSERT(pPaneShow != NULL);
pPaneShow->ShowWindow(SW_SHOWNA);
for (col = m_nCols - 2; col >= colNew; col--)
{
CWnd* pPane = GetPane(row, col);
ASSERT(pPane != NULL);
pPane->SetDlgCtrlID(IdFromRowCol(row, col + 1));
}
pPaneShow->SetDlgCtrlID(IdFromRowCol(row, colNew));
}
// new panes have been created -- recalculate layout
for (col = colNew + 1; col < m_nCols; col++)
m_pColInfo[col].nIdealSize =
m_pColInfo[col - 1].nCurSize;
m_pColInfo[colNew].nIdealSize = cxNew;
RecalcLayout();
}
void CSplitterWndEx::HideColumn(int colHide)
{
ASSERT_VALID(this);
ASSERT(m_nCols > 1);
ASSERT(colHide < m_nCols);
ASSERT(m_nHidedCol == -1);
m_nHidedCol = colHide;
// if the column has an active window -- change it
int rowActive, colActive;
if (GetActivePane(&rowActive, &colActive) != NULL &&
colActive == colHide)
{
if (++colActive >= m_nCols)
colActive = 0;
SetActivePane(rowActive, colActive);
}
// hide all column panes
for (int row = 0; row < m_nRows; row++)
{
CWnd* pPaneHide = GetPane(row, colHide);
ASSERT(pPaneHide != NULL);
pPaneHide->ShowWindow(SW_HIDE);
pPaneHide->SetDlgCtrlID(AFX_IDW_PANE_FIRST + row * 16 + m_nCols);
for (int col = colHide + 1; col < m_nCols; col++)
{
CWnd* pPane = GetPane(row, col);
ASSERT(pPane != NULL);
pPane->SetDlgCtrlID(IdFromRowCol(row, col - 1));
}
}
m_nCols--;
m_pColInfo[m_nCols].nCurSize = m_pColInfo[colHide].nCurSize;
RecalcLayout();
}
BEGIN_MESSAGE_MAP(CSplitterWndEx, CSplitterWnd)
//{{AFX_MSG_MAP(CSplitterWndEx)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
问:在我的程序中想在有些窗口打开或关闭时显示,隐藏某些工具条,这些我也都
做到了,但 我一直不能将那些工具条放置到我想放的位置,我的主工具条非常小,
它的右边可以放些工 具条(这就是我所想的)但它们总是显示在主工具条的下方.
答:我从一个示例文件中找到我现在的代码(抱歉我已经记不清从哪儿得来了).
I use the following code which I snitched from one of the examples (sorry,
don’t remember where).
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......
// create scalebar toolbar as generated by MFC wizard
....
DockControlBarLeftOf(&m_wndScaleBar,&m_wndToolBar );
}
void CMainFrame::DockControlBarLeftOf(CToolBar* Bar,CToolBar* LeftOf)
{
Crect rect;
DWORD dw;
UINT n;
// get MFC to adjust the dimensions of all docked ToolBars
// so that GetWindowRect will be accurate
RecalcLayout();
LeftOf->GetWindowRect(&rect);
rect.OffsetRect(1,0);
dw=LeftOf->GetBarStyle();
n = 0;
n = (dw&CBRS_ALIGN_TOP) ? AFX_IDW_DOCKBAR_TOP : n;
n = (dw&CBRS_ALIGN_BOTTOM && n==0) ? AFX_IDW_DOCKBAR_BOTTOM : n;
n = (dw&CBRS_ALIGN_LEFT && n==0) ? AFX_IDW_DOCKBAR_LEFT : n;
n = (dw&CBRS_ALIGN_RIGHT && n==0) ? AFX_IDW_DOCKBAR_RIGHT : n;
// When we take the default parameters on rect, DockControlBar will dock
// each Toolbar on a separate line. By calculating a rectangle, we in effect
// are simulating a Toolbar being dragged to that location and docked.
DockControlBar(Bar,n,&rect);
}
显示HTML模式的对话框类
作者:V. Rama Krishna 译:杨勇
我总是想在我的应用程序“关于”对话框中加入更多的图像,动画与音乐。但
它需要许多额外的工作,我没有胆量冒风险去做这些。幸运的是,
Internet Explorer 4.0的到来,在新的技术介绍中我们能做所有的这些而不
需要写大量的代码。一个办法是用web browser控件,但不总是最好的解决方
法,得别是对于模式对话框。这里另外的技术用在模式对话框-一种使用IE4的
技术引进。 模式对话框仅挂钩mshtml.dll(HTML描述与分析的动力)。因此许
多重要的功能如能够点击一链接和在同样的窗口中显示链接,但收藏夹与历史管
理是不起作用的(see Reusing the WebBrowser and MSHTML in Internet Client SDK)。
这是足够好的,几乎可放置在我们仅需要模式对话框的任何地方。
如何使用HTML模式的对话框
函数SHOWHTMLDIALOGFN在mshtml.dll库中,可以显示HTML对话框,但它通常需
要COM stuff 。为简单的做到这些我开发了CHtmlDialog类。所有的COM stuff
被隐藏,这个类能容易的使用到几乎所有的应用程序中。
一个简单的使用CHtmlDialog类的案例。
在这个案例中你需要将它用作为一个“关于”对话框。所有的你必须做的是写
一个HTML文件,并加入到资源文件中其ID为IDR_ABOUT_HTM。修改你的
OnAppAbout函数如下:
void CHDDemoApp::OnAppAbout()
{
CHtmlDialog dlg(IDR_ABOUT_HTM, AfxGetMainWnd());
dlg.DoModal();
}
这个构造函数也允许你使用字符串或URLs地址。详细的请看示例工程。正是通过
这个代码你能够 使用图像与声音。T
使用参数到对话框。
有时侯我们可能需要显示参数。在我们的应用程序中加入下列代码:
void CHDDemoApp::OnDemoParam1()
{
CHtmlDialog dlg(IDR_ABOUT1_HTM, AfxGetMainWnd());
CString str = m_strProductID //product ID
+ ";"+ m_strUserName //User Licensed + ";"
+ m_strCompanyName //Company Name
+ ";" + m_strAppVersion;
dlg.SetParam(str);
dlg.DoModal();
}
在HTML中我们有时要加入一些javascript/vbscript函数,如果你不熟悉
IE4 DHTM你可以跳过。下面是一个SCRIPT函数。
function getParameters()
{
var args = new Array();
args = window.dialogArguments.split(";");
//Now display in the document
Productid.innerText = args[0];
UserName.innerText = args[1];
CompanyName.innerText = args[2];
AppVersion.innerText = args[3];
}
从对话框中得到返回值:
现在我们可以使用Javascript和C++,我们能够做得更多。如何通过参数使使用
者在对话框中所做的事作为结果返回到C++程序中呢?下面这个示例,让我们
修改“用户名”和“公司名”,要做到这一点,我们必做到下面的关于
Javascript和C++的描述。
当HTML窗口被关闭时将调用
function window_onclose()
{
window.returnValue = UserName.value + ";" + CompanyName.value;
}
通过设定返回值,这个值被转移给C++代码。这个返回值可以是任何变量。在
我们的C++代码是通过调用GetReturnString或GetReturnVariant得到的。
dlg.DoModal(); //显示对话框
CString str = dlg.GetReturnString();
设置对话框尺寸
Tag:
问:对一个图形对象,我想用一种颜色来替换原位图调色板中的某一颜色。另
外不管 系统是处于16位,32位,256色是不是都可以用上面的方法解决。
答:我认为你必须对每种情况作出不同的处理,256色可以通过调色板来改
变某一颜色,但在 真彩系统中必须重绘图中的相应的点才可以。(位图是
真彩色的)
#define DSPDxax 0x00E20746
void CSJSizeBar::SwapBitmapColor(CDC* pDC, Cbitmap* pBitmap,
COLORREF rgbNew, COLORREF rgbOld)
{
BITMAP bm;
Cbitmap bmMask;
CDC memDC,maskDC;
// create memory dc for drawing
memDC.CreateCompatibleDC( pDC );
Cpalette* pOldPalette = (Cpalette*)memDC.SelectPalette(
pDC->GetCurrentPalette(),FALSE );
memDC.RealizePalette();
Cbitmap* Bitmap = memDC.SelectObject( pBitmap );
// fill out bitmap structure
pBitmap->GetBitmap( &bm );
// create mask
bmMask.CreateBitmap( bm.bmWidth,bm.bmHeight,1,1,NULL );
maskDC.CreateCompatibleDC( &memDC );
Cbitmap* bmOld = maskDC.SelectObject( &bmMask );
Cbrush brush( rgbNew );
Cbrush* brOld = (Cbrush*)memDC.SelectObject( &brush );
memDC.SetBkColor( rgbOld );
maskDC.BitBlt( 0,0,bm.bmWidth,bm.bmHeight,&memDC,0,0,SRCCOPY );
memDC.SetBkColor( RGB(255,255,255) );
memDC.SetTextColor( RGB(0,0,0) );
memDC.BitBlt( 0,0,bm.bmWidth,bm.bmHeight,&maskDC,0,0,DSPDxax );
maskDC.SelectObject( bmOld );
memDC.SelectObject( &brOld );
memDC.SelectObject( Bitmap );
memDC.SelectPalette( pOldPalette,FALSE );
}
问:我在MDI程序中增加了一个CRichEditView文档模板,在子窗口视中我增加了下
面一些代码.
StartReport (void)
{
CReportFrame *rpt;
CReportDoc *rptDoc;
// First get the right document template
POSITION pPos = theApp.GetFirstDocTemplatePosition();
theApp.GetNextDocTemplate ( pPos );
theApp.GetNextDocTemplate ( pPos );
CDocTemplate *pTemplate = theApp.GetNextDocTemplate ( pPos );
// Verify validity
ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);
// Create the frame
rptDoc = new CReportDoc;
rpt = (CReportFrame*)pTemplate->CreateNewFrame ( rptDoc, NULL );
pTemplate->InitialUpdateFrame (rpt, rptDoc);
// Get access to the display area
CReportView *rptView = static_cast
(rpt->GetActiveView());
CRichEditCtrl &rptCtrl = rptView->GetRichEditCtrl();
}
CReportFrame继承于CMDIChildWnd
CReportDoc继承于CRichEditDoc
CReportView继承于from CRichEditView
如果我关闭程序前不关闭新建的视,调试器将认为程序依然在运行(程序管理器
中依然存在) 我需要用调试菜单中的stop debugging来关闭程序;如果我手工关
闭该视,程序将会正常 关闭.如果有什么不同的话,在手工关闭新的视之前程序会
询问是否保存. 那么怎样我才能关闭程序呢?
答:1)我也碰上过对话框,窗口不能自动关闭的情况,这主要是因为继承的对象不正
确所造成的。 通常应该在主程序中设置AfxGetMainWnd().
你的程序让我搞糊涂了,一连使用了多个GetNextDocTemplate(pPos),在这些
文档指针是NULL时 通常会引起一些循环.在你的文档模板中是否已经精心算好
了数目?这样可能会产生些bugs 我建议找出当前的文档模板用
CDocTemplate::CreateNewDocument()来代替你的"new CReportDoc"
2) 记住一个公共规则,关闭程序前要关闭所有的视.
作者:俞良军
在MFC中,用列表框(CListBox)来显示多个字符串是一种很方便的方法。但缺
省的列表框水平滚动条不够智能——这里智能的含义是:在应该出现的时候出
现,不应该出现的时候消失,而且应能自动调节自己的大小。本文通过实例说
明了存在的问题和解决办法。
---- 一、问题演示
---- 首先用Visual Studio应用向导创建工程CustomCListBox。这是一个基于对
话框的应用,向导提供的所有可选参数均采用其缺省值。
---- 在资源编辑器中将对主话框字体设为宋体12,插入一个CListBox控制,设
其ID为IDC_LLISTTEST,大小为125 X 84。 请确认列表框的垂直滚动条、水平滚
动条有效,取消其排序风格。
---- 启动Class Wizard,选择Member Variables选项卡,为列表框加入对应的
成员变量m_lListTest,在Category中选择Control。
---- 接下来在Workspace窗格中选择ClassView,扩展CCustomCListBoxDlg类并
双击OnInitDialog(),在编辑窗格中找到注释行“TODO: Add extra initialization here”,
在该行下面加入以下内容:
m_lListTest.AddString(_T("One"));
m_lListTest.AddString(_T("Two"));
m_lListTest.AddString(_T("Three"));
m_lListTest.AddString(_T("Four"));
m_lListTest.AddString(_T("Five"));
m_lListTest.AddString(_T("Six"));
m_lListTest.AddString(_T("北国风光,千里冰封,万里雪飘。"));
m_lListTest.AddString(_T("Eight"));
m_lListTest.AddString(_T("Nine"));
m_lListTest.AddString(_T("Ten"));
---- 编译并运行这个工程,可以发现列表框能够正确显示全部内容。
---- 如果在上述m_lListText.AddString(_T"Ten"))后面加入一行:
m_lListTest.AddString(_T("Eleven"));
---- 重新编译并运行该工程,可以发现出现了一个垂直滚动条。垂直滚动条的出
现使得列表框水平方向有效显示宽度变小,第七行的内容被切割而不能完整显
示。但此时水平滚动条并没有自动出现,第七行被切割部分就无法看到了。
---- 如果我们删除最后加入的语句,把第七行汉字加长到超出列表框显示宽度
为止,也可以发现水平滚动条不会自动出现。被切割部分仍旧无法看到。
---- 由此可知,CListBox的水平滚动条并不象垂直滚动条那样“聪明”:垂直
滚动条总是能够在需要它的时候自动出现,并能够自动调节自身大小,而水平
滚动条不能。
---- 二、解决问题
---- 为提高代码的可重用性,可以创建CListBox的派生类,在派生类中实现
“智能”水平滚动条。需要考虑的主要问题包括:跟踪最大字符串宽度(应能适
应不同场合下的字体变化),必要时计算垂直滚动条宽度,自动显示和调节水平
滚动条的大小。
---- 选菜单 Insert/New Class,设新创建类的名字为CDJListBox,其基类为
CListBox,其它选项采用缺省值。单击OK,Visual Studio自动生成
DJListBox.cpp和DJListBox.h两个文件。
---- 接下来将主对话框的列表框改为CDJListBox类型,即在CLassView扩展
CCustomListBoxDlg类并双击m_lListTest成员,在编辑窗格,修改
CListBox m_lListTest;
---- 为:
CDJListBox m_lListTest;
---- 然后,在类声明代码之前,插入
#include "DJListBox.h"
---- 此时如果重新编译并运行,是无法看到任何实质性的改变的,因为我们并
没有修改CDJListBox。所有对于CDJListBox的调用都直接传递给基类CListBox了。
---- 跟踪字符串最大宽度可以通过覆盖CListBox::AddString()实现。打开DJListBox.h,
紧接类的析构函数加入如下声明:
int AddString( LPCTSTR lpszItem );
---- 并在实现文件DJListBox.cpp加入该函数框架:
int CDJListBox::AddString(LPCTSTR lpszItem)
{
//此处加入字符串宽度跟踪、水平滚动条显示等代码
}
---- 字符串宽度跟踪可以用整形成员变量m_nMaxWidth实现。在DjListBox.h的
protected声明区内,加入以下一行:
int m_nMaxWidth;
---- 在DJListBox.cpp文件,找到CDJListBox的建构函数,为这个最大宽度作
初始化:
m_nMaxWidth = 0;
---- 现在可以改动新加入的AddString()了。先应该调用基类AddString(),
并用nRet记录其返回值:
int nRet = CListBox::AddString(lpszItem);
---- 接下来调用GetScrollInfo()以获得垂直滚动条的相关信息。这些信息是
通过一个SCROLLINFO结构传递的,下面是对该结构初始化并调用GetScrollInfo()的代码:
SCROLLINFO scrollInfo;
memset(&scrollInfo, 0, sizeof(SCROLLINFO));
scrollInfo.cbSize = sizeof(SCROLLINFO);
scrollInfo.fMask = SIF_ALL;
GetScrollInfo(SB_VERT, &scrollInfo, SIF_ALL);
---- 在调试器内观察SCROLLINFO,可以发现要获得nMax和nPage的正确数值,
列表框至少应含有一个字
在你的对话框中加入下代码,可实现拖动对话框的任何地方拖动对话框。
void CNCHitDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialog::OnLButtonDown(nFlags, point);
// fake windows into thinking your clicking on the caption, does not
// maximize on double click
PostMessage( WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM( point.x, point.y));
}
或
UINT CNCHitDlg::OnNcHitTest(CPoint point)
{
UINT nHitTest = CDialog::OnNcHitTest( point );
// also fake windows out, but this maximizes
// the window when you double
// click on it.
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
}
问:如果我在文件对话框中选择了某个文件,然后点击"打开"按钮,DoModal()函数
返回 1(IDOK),但如果我选择了多个文件,DoModal()函数却返回2(IDCANCEL)
答:如果设置了OFN_ALLOWMULTISELECT标志,而又选择了多个文件时.指针指向一
个可以容纳 所选择文件名的区域.这个缓冲区包括了所选择的文件的文件名以
及路径.在Explorer 类型的对话框中,路径和文件名字符串是用NULL分隔的,在
最后一个文件名中以NULL结尾. 老式的对话框中,串是用空格来分隔的.你可以
使用FindFirstFile函数来转换长文件名 和短文件名,如果这个缓冲区太小的
话,函数将会返回FALSE,CommDlgExtendedError() 函数返回
FNERR_BUFFERTOOSMALL,在这种情况下,最前两个字节指出缓冲区应该的大小,
单位是字节或者是字符数.
nMaxFile
指定缓冲区的大小,以一个字节为单位(ANSI或UNICODE),如果该区域太小不能容
纳 文件信息时,GetOpenFileName和GetSaveFileName函数返回FALSE.该区域至少
为 256字符数
简单地说,你需要提供比文件对话框提供的空间一个更大的空间,前者只有
_MAX_PATH长.
2)你是否调用了CommDlgExtendError取得更详细的资料?
我想可能是给OPENFILENAME结构的缓冲区太小了,MFC中只提供了
_MAX_PATH或260个字符 而这对选择多个文件来说,显得小了些.
你可以调用CommDlgExtendedError函数,应该返回一个FNERR_BUFFERTOOSMALL,
参照 文件说明中的OPENFILENAME结构.
---- 在使用VC++5.0开发应用程序时,我们可能需要改变框架窗口(包括主框架窗口和
子框架窗口)的图标,而不是使用VC为你已经准备好了的、很难看的那个图标。但笔者
参考了很多书籍、资料,却没有找到任何叙述如何去改变窗口图标的方法的文章(或许
很多人认为这根本就不值得一提),让很多初学者无从着手。笔者经过实践,发现了以
下两个改变框架窗口图标的方法。使用方法一可以在编写应用程序时指定框架窗口的图
标,使用方法二可以在程序运行时根据需要动态地改变窗口的图标。如果把这两个方法
结合起来,就可以随心所欲改变窗口的图标。
---- 方法一、在编程时指定窗口的图标
---- 一、如果是指定主框架窗口的图标,其步骤如下:
创建或打开工程Icon(以下都以工程名为Icon为例)。
单击Workspace窗口的ResourceView标签,选中资源ID为IDR_MAINFRAME图标资源,然后
按Delete键把它删除掉。注意:一定要把它删除才行。
从Developer Studio的Insert菜单中选择Resource,然后选择Icon,新建(New)一个
新的图标或导入(Import)一个已有的图标。
把新图标的资源ID改为AFX_IDI_STD_MDIFRAME(如果是MDI应用程序)或改为
AFX_IDI_STD_FRAME(如果是SDI应用程序)。AFX_IDI_STD_MDIFRAME和
AFX_IDI_STD_FRAME这两个资源ID是MFC中预定义了的。
编译并运行程序,可以发现主框架窗口的图标就是你指定的图标。
---- 二、如果是指定MDI子框架窗口的图标,其步骤与上述相似。
同上。创建或打开工程Icon。
删除资源ID为IDR_ICONTYPE(在你的工程中应该是IDR_XXXTYPE,其中XXX为你的工程
名)图标资源。同样要注意的是:一定要把它删除才行。
同上。新建(New)一个新的图标或导入(Import)一个已有的图标。
把新图标的资源ID改为IDR_ICONTYPE(即步骤2中删除的资源ID)。
编译并运行程序,可以发现子框架窗口的图标就是你指定的图标。
---- 用这个方法,可以在多视图类MDI应用程序中为不同视图的子框架窗口指定不同的
图标。
---- 方法二、在程序运行时动态地改变窗口的图标
---- 在程序运行时动态地改变框架窗口图标的原理是使用函数
---- CWnd::SendMessage()向窗口发送WM_SETICON消息。其方法是:
HICON hIcon=AfxGetApp()- >LoadIcon(IDI_ICON1);
ASSERT(hIcon);
AfxGetMainWnd()- >SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);
---- 以上叙述中的AfxGetMainWnd()是获得主框架窗口的窗口句柄,所以改变的是主框
架窗口(包括MDI和SDI)的图标,用同样的方法略作改动就可以改变MDI应用程序子框
架窗口的图标。
---- 下面举一个实例来说明如何改变主框架窗口的图标,步骤如下:
---- 1.创建或打开工程Icon。
---- 2. 从Developer Studio的Insert菜单中选择Ressource,然后选择Icon,新建
(New)或导入(Import)两个图标,并资源ID分别改为IDI_ICON_GREEN,
IDI_ICON_RED。
---- 3.单击Workspace窗口的ResourceView标签,对IDR_MAINFRAME菜单资源进行编
辑。在View菜单中加入一个分隔符和Green Icon、Red Icon两个菜单项。其资源ID分别
改为ID_VIEW_GREEN和ID_VIEW_RED。
---- 4.为主窗口添加如下消息处理函数:
// CMainFrame message handlers
void CMainFrame::OnViewGreen()
{
// TODO: Add your command handler code here
HICON hIcon=AfxGetApp()- >LoadIcon(IDI_ICON_GREEN);
ASSERT(hIcon);
SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);
file://因为是在类CmainFrame中,所以不需要用
AfxGetMainWnd()- >SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);
}
void CMainFrame::OnViewRed()
{
// TODO: Add your command handler code here
HICON hIcon=AfxGetApp()- >LoadIcon(IDI_ICON_RED);
ASSERT(hIcon);
SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);
}
---- 5. 最后编译并执行程序,执行View菜单的Green Icon和Red Icon,可以看到成功
得改变主框架窗口的图标。
---- 以上实例是在程序运行时改变主框架窗口的图标,可以用同样的方法改变MDI程序
的子框架窗口的图标,有兴趣的朋友可以一试。
在使用VC6.0/5.0的AppWizard生成MDI应用的时候,我们发现MDI主窗口的客
户区背景千篇一律的是深灰的。VC6.0/5.0并没有提供修改其背景色的方法。甚
至使用SDK编程也没有好的方法修改背景色。以至于微软的产品如Office也是灰蒙
蒙的背景。那么,有没有办法将背景设置为自己喜欢的颜色呢?
笔者在学习过程中摸索出一套随意改变客户区窗口颜色的方法。利用这套方法,
可以将客户区窗口设为256色背景甚至设为BITMAP位图以至于动画等等。大大地增
强了程序的多媒体效果。
先介绍对MDI客户窗口编程的基本原理。
一、MDI客户窗口
一个MDI应用的主框架窗口包含一个特殊的子窗口称为MDICLIENT窗口。
MDICLIENT窗口负责管理主框架窗口的客户区。MDICLIENT窗口本身有自己的
子窗口即由CMDIChildWnd派生的文档窗口,也就是MDI子窗口。MDI主框架窗口
负责管理MDICLIENT子窗口。当控制条(菜单条,状态条等)发生变化时,MDI主
框架窗口重新配置MDICLIENT窗口。MDICLIENT子窗口负责管理全部的MDI子窗口。
父窗口负责将某些命令传递到子窗口。因此,消息队列发向MDI子窗口的消息
由MDICLIENT窗口负责传递,发向MDICLIENT窗口和MDI子窗口的消息由主框架窗
口负责传递。这样,我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息
然后加入自己设计的代码。
二、MDI客户窗口编程方法
对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被
MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不
起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使
用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单
地重载WM_PAINT的消息处理函数。
改变MDI客户窗口背景的方法有两种。
使用CMDIFrameWnd::CreateClient 函数:
CreateClient( LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu );
参数lpCreateStruct是指向CREATESTRUCT 结构的指针。在CREATESTRUCT 结构
中lpszClass项指向窗口类WNDCLASS结构。通过改变WNDCLASS结构中的
HbrBackground项和hCursor项可以更改MDICLIENT窗口的背景刷和光标。由于该
函数创建新的MDICLIENT窗口对象,必须在重载的主窗口的OnCreate成员函数中
调用。该方法比较复杂,必须手动创建MDI客户窗口,不能利用AppWizard自动
提供的功能。而且,只能使用Windows95有限的16色背景刷。本文采用第二种方法。
在主框架窗口的消息队列中截获发向MDI客户窗口的WM_PAINT消息并向主框架窗
口发送一条标志消息。在这条标志消息的处理函数中对MDI客户窗口进行操作。
该方法比较简捷,但有几点值得注意的地方。
首先,如何截获MDI客户窗口WM_PAINT消息。MFC提供
了PreTranslateMessage(MSG* pMsg) 函数。它在消息发送到TranslateMessage
和DispatchMessage 函数以前预先解释消息。可以重载该函数截获MDI客户窗
口WM_PAINT消息:
BOOL PreTranslateMessage(MSG* pMsg)
{
if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT)
PostMessage(WM_PAINT);
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
其次,为简单起见,这里将标志消息设为MDI主窗口的WM_PAINT。在MDI主窗
口WM_PAINT的消息处理函数中增加重画MDI客户窗口的代码。读者也可以自定义
消息,不过麻烦一点。
最后,由于对MDI客户窗口的操作都是在主窗口完成的。如何在主窗口中获得
MDI客户窗口的设备描述表呢。其实,在MDI主窗口类中有MDI客户窗口成员
m_hWndMDIClient。按如下方法得到客户窗口的设备描述表
dc.m_hDC=::GetDC(this->m_hWndMDIClient);
然后就可以对客户窗口进行操作了。
三、实例
将客户窗口设为256色背景。
使用AppWizard生成MDI应用TEST。
在TEST.CPP中的函数,增加如下代码:
BOOL CTestApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(); // Load standard INI file options (including MRU)
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TESTTYPE,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
大家在使用某些软件的过程中,有没有注意到有些软件有一些很有趣的东西。
比如说在主窗口的标题栏上居然有一个按钮。在Internet中随处可见这样的小
控件。按钮怎么可以加入到非客户区(Client)呢?
在这里,最关键的一点就是,大家不要被传统知识误导:真的认为它是
一个按钮。有名柄(handle)的控件当然不能放在标题栏上了。有经验的程序员
用Spy++跟踪一下的话,马上就会发现其中的秘密。它并不是一个按钮,只不
过是处理成按钮的样子罢了。
既然知道了所以然,那么我们为什么不能自己来做一个呢,当然没问题,下面我们就用
Delphi来实现它,讲注意我的注解。
在具体实例之前,我们应该知道几个关于标题栏的重要的消息:
WM_NCPAINT:重画标题栏消息。我们必须截住它,可以在这里重画按钮;
WM_NCLBUTTONDOWN:在标题栏上按下鼠标左键消息。我们可以截住它,在标题栏上画出
按钮按下的样子,并且可以在其中进行自已的单击事件的处理,使得它像一个按钮;
WM_NCLBUTTONUP:在标题栏上释放鼠标左键消息。我们可以截住它,在标题栏上画出按
钮弹起的样子;
WM_NCLBUTTONDBLCLK:在标题栏上双击鼠标左键消息。我们可以截住它,当在按钮区域
双击时,我们就该使其无效,从而避免窗体执行最大化和还原操作。
WM_NCRBUTTONDOWN:在标题栏上按下鼠标右键消息。我们可以截住它,当在按钮区域双
击时,我们就该使其无效,从而避免弹出窗体按制菜单。
WM_NCMOUSEMOVE:在标题栏上移动鼠标消息。我们可以截住它,当鼠标移出按钮区域
时,我们就必须画出按钮没有被按下,即凸起时的样子。
WM_NCACTIVATE:当标题栏在激活与非激活之间切换时收到该消息。我们可以截住它,
当该窗口处理激活状态时,我们可以做一些事情,比如说将我们的标题栏按钮上的字体
变灰或变黑来指示该窗口的当前状态。下面我没有加入该项功能,如果大家感兴趣的
话,可以自己完成。
(大家从这里可以发现,标题栏的消息都是WM_NC开头的)
实例文件如下:
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Menus;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
CBBtnRect: TRect; // Caption Bar Button Rectangle
CBBtnFont: TFont; // Caption Bar Button Font
procedure DrawCaptionBtn(uEdge: UINT);
// 当在标题栏上按下鼠标左按钮时进入该过程
procedure WMNcLButtonDown(var m: TMessage); message WM_NCLBUTTONDOWN;
// 当在标题栏上放开鼠标左按钮时进入该过程
procedure WMNcLButtonUp(var m: TMessage); message WM_NCLBUTTONUP;
// 当在标题栏上移动鼠标时进入该过程
procedure WMNcMouseMove(var m: TMessage); message WM_NCMOUSEMOVE;
// 当在标题栏上双击鼠标左铵钮时进入该过程
procedure WMNcLButtonDBLClk(var m: TMessage); message WM_NCLBUTTONDBLCLK;
// 当在标题栏上按下鼠标右按钮时进入该过程
procedure WMNcRButtonDown(var m: TMessage); message WM_NCRBUTTONDOWN;
// 当画标题栏时进入该过程
procedure WMNcPaint(var m: TMessage); message WM_NCPAINT;
// 当标题栏在激活与非激活之间切换时进入该过程
procedure WMNcActivate(var m: TMessage); message WM_NCACTIVATE;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.DrawCaptionBtn(uEdge: UINT);
var
hCaptionDC: HDC; // 标题条Device Context
hOldFont: HFONT; // 原来的字体
r: TRect;
begin
hCaptionDC := GetWindowDC(Self.Handle); // 注意不能用GetDC,那样的话,将得不
到标题栏
// 的设备上下文
file://画按钮的样子,如果uEdge=EDGE_RAISED,则画出的样子为凸起;如果
file://uEdge=EDGE_SUNKEN,则画出的样子为凹下。
DrawEdge(hCaptionDC, CBBtnRect, uEdge, BF_RECT or BF_MIDDLE or
BF_SOFT);
file://设置标题栏的设备上下文为透明状态
SetBkMode(hCaptionDC, TRANSPARENT);
file://设置标题栏设备上下文的字体
hOldFont:= SelectObject(hCaptionDC, CBBtnFont.Handle);
file://画按钮
if uEdge = EDGE_RAISED then
DrawText(hCaptionDC, ’Caption Bar Button’, 18, CBBtnRect, DT_CENTER)
else begin
r := CBBtnRect;
OffsetRect(r, 1, 1);
DrawText(hCaptionDC, ’Caption Bar Button’, 18, r, DT_CENTER);
end;
file://还原为原来的字体
SelectObject(hCaptionDC, hOldFont);
end;
procedure TForm1.WMNcActivate(var m: TMessage);
begin
inherited;
DrawCaptionBtn(EDGE_RAISED);
end;
procedure TForm1.WMNcPaint(var m: TMessage);
begin
inherited;
DrawCaption