分类: C/C++
2011-07-18 09:16:35
注:本文是我在CSDN看到了一篇很精彩的技术讨论帖,所以转过来,鞭策自己。
以下是原文(我自己整理了一下)
问题的提出:
楼主olion
怎样修改CEdit中的文本的字体大小及颜色?
1 楼andygood
为什么总是有人问这种入门问题呢?呵呵。
重载对话框的WM_CTRLCOLOR消息处理。
在下例中,m_edit是一个文本框对象(属于对话框成员)。
HBRUSH CTestDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if(pWnd == &m_edit)
{
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(0,0,255));
HBRUSH newHbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
// TODO: Return a different brush if the default is not desired
return newHbr;
}
return hbr;
}
另外也可以从CEdit派生一个类,重载它的OnPaint函数。
2 楼iceberg
andygood所提供的方法并不是最好的方法。从软件工程的角度来讲,最好是从CEdit类派生出一个可以自己处理字体、背景和字体颜色的类。而根据你的要求,最快的实现方法便是利用MFC独特的消息反射机制,让edit控件自己完成颜色的设置。
我们知道,当控件要重画时,一般会向自己的父类发送一个WM_CTLCOLOR(andygood兄,你把该消息的名字都搞错了,不是 WM_CTRLCOLOR:),要求父类提供一个DC来完成重绘.按andygood的方法,如果对话框上有20个edit control的话,你岂不是要针对每一个控件去判断和处理吗?何不让这些edit控件自己知道怎么重画呢?所以,我们可以利用一种高级的MFC技术-- 消息反射(Message Reflection)来完成.所谓消息反射是指将控件发给父类的一些通知消息再返回给控件本身,让控件自己处理.可以反射的消息有很多,除了 WM_CTLCOLOR外,还有WM_NOTIFY,WM_DRAWITEM等等,我们可以利用消息反射实现一个可以自定义颜色和字体的类,一劳永逸,把 它放在VC的Components Gallery里随时使用了.
下面是如何操作:
1. 在你的工程中新建一个类,即打开ClassWizard,点Add Class,然后选New,新类从CEdit派生,就叫它CColorEdit吧.点OK,CColorEdit类即被加入到你的项目中.
2.切换到Class View视图,在CColorEdit上点右键,然后选"Add Windows Message Handler",在弹出的对话框中可以看到很多可以处理的消息.注意了没有,其中有一个消息为"=WM_CTLCOLOR",前面加的'='号表示它是 一个反射的消息.选中这条消息,点Add handler,再点OK,Classwizard自动为你完成消息映射.
3.你的CColorEdit类会有一个HBRUSH CColorEdit::CtlColor(CDC* pDC, UINT nCtlColor) 的成员函数.你可以为该类声明两个成员变量,分别表示前景色和背景色.既然你有了重绘的pDC,剩下的代码就和andygood兄所提供的差不多了.
完成后,你只要指定edit控件的前景色和背景色,其他的事情,它自己会做了,也不必劳烦父类了.
总之,善于使用消息反射的话,有时可以使你事半功倍.
5 楼andygood
世界上并没有最好的方法。
下面我讲一下我的方法的优点:
1、可以批处理。
在OnCtlColor的参数中,有一个UINT nCtlColor的参数,可以取如下值:
CTLCOLOR_BTN Button control
CTLCOLOR_DLG Dialog box
CTLCOLOR_EDIT Edit control
CTLCOLOR_LISTBOX List-box control
CTLCOLOR_MSGBOX Message box
CTLCOLOR_SCROLLBAR Scroll-bar control
CTLCOLOR_STATIC Static control
我举的例子是针对某个控件的,但如果使用nCtlColor来判断的话只需要两、三行代码,即可设置全部同类控件的属性(当然同时亦可设置单个控件的特殊属性)。难道你的所谓面向对象方法比这个还快吗?
2、符合模块化设计。
如果使用上述方法,你可以随便先用资源编辑器做n个控件,并使用DDX来交换数据。比如你把一个编辑框设为CString类型的变量,然后可以在 WM_CTLCOLOR处理里面再设置其文字、背景色。这样,你就把数据处理与显示处理分开了。如果使用iceberg说的方法,使用派生类,面向对象是 没错了,但是,你怎么把一个控件即设为Value类型,又把它设为Control类型呢?我想知道。人们使用对话框,一方面原因就是可以通过使用DDX把 变量设为Value类型。如果抛弃这一点,是不是很傻?呵呵。
3、消息反射并不总是有效的,有时子类的消息只能在父类的消息表中处理,或者在父类中处理更容易。你随便看几个使用SDK开发的程序,有几个使用消息反射?
4、写的代码最少,使用文件最少,生成的可执行文件最小。
不言而喻。
类似的优点想找还有。我不是想抬杠,而是想告诉大家一个道理:不管黑猫白猫,抓老鼠抓的好的就是好猫。
6 楼olion
谢谢各位,我现在改变背景之类的行了,但改变字体大小总不行,麻烦看看我的代码有何错误?
HBRUSH CTestDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
CFont f;
if(pWnd == &m_edit)
{
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(0,0,255));
HBRUSH newHbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
LOGFONT logFont;
logFont.lfHeight=36;
logFont.lfWidth=0;
logFont.lfEscapement=0;
logFont.lfOrientation=0;
logFont.lfWeight=FW_NORMAL;
logFont.lfItalic=0;
logFont.lfStrikeOut=0;
logFont.lfCharSet=ANSI_CHARSET;
logFont.lfOutPrecision=OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision=OUT_DEFAULT_PRECIS;
logFont.lfQuality=PROOF_QUALITY;
logFont.lfPitchAndFamily=VARIABLE_PITCH|FF_ROMAN;
strcpy(logFont.lfFaceName,"Times New Roman");
f.CreateFontIndirect(&logFont);
pWnd->SetFont(&f,FALSE);
return newHbr;
}
return hbr;
}
7 楼iceberg
To andygood:
andygood,你又犯了一个大错误,谁说用消息反射派生的类不能使用DDX?你试过吗,我可试过了,工作得非常之好,你若不信,我发一个demo给你如何?所以,你还需要好好看看MFC的源码,看看DDX到底是怎么实现的。
至于模块化嘛,老兄,现在可是面向对象的时代了!你的意思是面向对象的方法就不符合模块化设计了吗?嘿嘿,你得好好看看软件工程和面向对象的书,面向对象 技术是最集中的体现了模块话设计的优点,同时克服了模块化设计中代码和数据分离的致命弱点,你凭什么说我的面向对象的方法没有你的模块化(其实也不是模块 化,你不也是在使用其父类么?)优越呢?
说一句题外的话,我们做程序的,一定要有实事求是的精神,用事实来说话,自己没有把握的,最起码不要乱说,不然会贻笑大方,还落得个“井底之蛙”之嫌:)
现在我说说怎样实现派生类得DDX.很简单,在资源编辑器中用Classwizard添加控件的成员变量,如果是添加控件类型的变 量,Classwizard会用CEdit作为类型,没关系,在ClassWizard生成的代码中把控件变量的声明改为CColorEdit即可(别忘 了包含头文件),至于添加Value型或CString型来DDX 控件的值,由Classwizard代劳即可,也就是说,如果做好了CColorEdit类,那么在其他项目中要使用它,所要做的无非是把源文件加入项 目,然后在把CEdit改为CColorEdit,一切搞定,用不着每个项目都要在父类里去处理什么消息了,孰快孰慢,明白人一看就知.
To olion:
你代码的问题在于,你要把你指定的font选进设备场景(pDC)中,具体来说是用
pDC->SelectObject(CFont* pFont)函数将你指定的字体选进设备场景.试试看:)
至于字体的创建,我建议你用CFont类.为什么pWnd->SetFont()不起作用呢?你看看CEdit的实现源码就知道了:)
8 楼andygood
Faint iceberg:
You don't know what I really mean.
举个简单的例子。比如我需要一个类型为int的编辑框。
按照我的方法,可以如下实现:
1、在资源编辑器中编辑控件,包括设置类型与数值范围。
2、在OnCtlColor中设置颜色。OK!完了。
按照你的方法:
1、在工程中加入CColorEdit类的文件。
2、因为这个类不是通过ClassWizard自动生成的,在类浏览信息中不包含它,所以必须先关闭工程(哈哈),在资源管理器中删除类信息文件,然后再打开工程,重新生成类信息文件(有这个功夫可以喝杯茶了)。
3、在资源编辑器中编辑控件,设置控件为CColorEdit类(如果不重建类信息文件,这一步必须手动执行),设置数据类型与范围。
4、在对话框头文件中加入include文件。(切换了几个文件了)
5、在OnInitDialog中设置Edit的颜色。如果使用默认颜色,这一步可以略过。(PS:难道你每个对象都使用一种颜色吗?)
傻瓜也看得出来哪种方法简单、快捷。难道你真的看不出来吗?(或者你知道比我说的方法快的方法?请赐教)
另外,你有没有做过超过几百个类的工程呢?如果大家都象你这样喜欢一点功能就用一个类来表示的话,后果将不堪设想(真的)。如果设计Windows的人就你这水平的话,恐怕256M内存的机器也跑不了Windows。建议你少看点书,多做点系统级的设计。
9 楼andygood
忘了说一句,因为我从来不这么用,所以对DDX的理解没有iceberg那么深刻,在此对我的浅见表示道歉。
10 楼iceberg
To andygood:
我觉得对如此简单地一个问题再争论下去是没有含义的。也许你觉得在这么一个简单的任务中你的方法很直接,但你可能没有接触过大的项目,我所接触到的一个 GIS系统,光类就有上百个,有些类的代码达到5、6千行。这样的大型项目,没有面向对象的设计方法,项目的管理、修改、维护简直就是一场噩梦。我发这个 帖子最大的目的是揭示一种从面向对象角度出发解决问题的思路,这是一个非常好的习惯。网上有很多MFC控件就是这么写出来的,我估计你没有写过什么MFC 控件(不然怎么会不知道DDX的实现细节呢,这是写MFC控件的基础)。我所接触的GIS系统恰恰如你所说,有时候,很小的功能也封装得很好,因为这些小 功能是实现更复杂的功能的基础。这个GIS平台也异常的健壮和稳定,速度也没有话说。所以说,越是系统级设计,越是需要面向对象的思想。如果设计 windows的人都象你一样的话,我估计windows的下一个版本要n年才能推出来(至少象我这样的人写出的windows还可以运行:)。所以,你 很需要大型项目的熏陶,作为大型软件项目,健壮性,良好的可维护性和可修改性才是最重要的。节省零点几秒的启动时间完全没有含义。
我觉得你所说的文件大小啊,速度啊非常可笑,C++代码也是充分优化的,即便是你的代码快一点点,你测的出来吗?你的文件是会比我的小一两K 的字节,但你很在乎这一两K吗?如果这么说,那么DELPHI程序员们都会发疯的(我也用DELPHI写程序:)。
所以说,连书都没有看过,还奢谈什么系统级设计呢?你的系统级设计不喜欢用对象封装的吗?你知不知道考系统分析员,最重要就是面向对象的分析设计方法呢?你不会已经通过系统分析员了吧:)
好了,好了,到此为止,说着说着说到软件工程去了。如果哪一天,你要考系统分析员,我们可以继续交流探讨。不管怎么说,希望这样的讨论对大家都有所帮助。同时也希望我们可以在其他的技术领域继续交流。
~~~~~~~~完~~~~~~~~~~
谢谢andygood, iceberg两位.经过这一讨论明白了好多理论和道理。当然也有没有解决好的,
6 楼olion 的问题我曾经遇到过,问题的关键是CFont f是一个局部变量,出了这个函数就不存在了,这样的字体到哪找去,应该是这样的就解决问题了
CFont *f;
f=new CFont;
这样就能保证是正确的。