huaxiazhihuo

 

类设计一则,GDI对象选入器

        虽然很痛恨MFC,但还是要经常使用MFC开发界面,虽然MFC怎么怎么的不好,但还是可以应付一般的界面要求,而且其运行效率也可以接受,关于这一点,它在WINDOWS3.1之时,就已经能胜任了,并且在下现在使用MFC,基本上也能随心所欲了,想怎么整就怎么整。为了减少以后浪费在MFC上的时间,我决定重构一套著名的MFC上的界面库。界面库这个东西,大家都知道,其各种美观的界面,基本上都是一笔一笔画出来的,代码中大量地使用了DC的各种操作,不可避免,就一再出现了好比以下类似的代码:

    HPEN pen = CreatePen(……);
    HPEN oldPen 
= (HPEN)SelectObject(hDC, oldPen);
    SelectObject(hDC, pen);
      ........
    DeleteObject(pen);

 

 

        这些代码,既无味,写起来又容易出错,它存在3个很明显的问题:1SelectObject使用了类型转换,一不小心,自然就转换错了,MFC中通过对象指针,内部做了转换,避免了这个问题;2、要将旧有的GDI对象选回DC中,很容易就遗忘了这一步代码,并且如果要同时操作几个DC的时候(这种情况较少见),在选回旧GDI对象时,那就更容易出错了;3、还要删除对象,这个,MFC中通过GDI对象的构造函数来消除这个问题。这样也罢了,但还有一个问题最让人不能忍受的,就是,代码中要定义一个旧的GDI对象变量,以最后选回设备环境之中,因为我最讨厌定义新变量,新函数了,每次都要变量名和函数名琢磨半天,当然,最讨厌的还是编写重复的代码了。于是,我决定编写一个类,以免总是要写这些毫无新意的重复代码,希望这个类是这样使用的:

CXGdiObjectSelector selector(hDC);
selector.CreatePen(……);
selector.SelectBrush(brush);
  ........

        不需要定义旧的GDI对象变量,画图完成之后,也不需要选回设备环境之中了,也不需要手工删除之前创建的GDI对象,一切,这个类都帮你代劳了,咦,这么神奇,它是如何做到的,自然是析构函数,再次向伟大的析构函数致以最高敬意。这个类,比之于刚开始的手工打造,它需要执行构造函数,以保存hDC到内部成员变量,很明显,多了一步赋值操作,此外,可以预料,这个类里面应该还有其他的初始化操作,这又是不可避免的多余代码。当然,这里的多余,都是我能接受的,我也深知,既要马儿,又要马儿不吃草,那是不可能的神话。但是,在实现这个类的时候,我想了种种办法,包括模板元编程也祭上了,始终还是存在其他多余的操作,再次审视刚刚开始的一段代码,不得不承认,它丑是丑了点,但执行的效率确实真他妈的高,而且所占的空间也很少,全部没有一丁点多余之处。好了,进入我们的类的实现。

class CXGdiObjectSelector
{
public:
    CXGdiObjectSelector(HDC hDC)
    
{
        ASSERT(hDC 
!= NULL);
        m_hDC 
= hDC;
        m_nSelectedFlags 
= 0;
        m_nCreatedFlags 
= 0;
    }


    
~CXGdiObjectSelector();

    
void SelectPen(HPEN pen)
    
{
        replaceObject(pen, XFLAG_PEN);
    }


    
void SelectBrush(HPEN brush);
      ........

    
bool CreatePen()
    
{
        HPEN hPen 
= CreatePen();
        
if (hPen == NULL)
            
return false;
        replaceObject(hPen, XFLAG_PEN);
        m_nCreatedFlags 
|= XFLAG_PEN;
        
return true;
    }

      ........

private:
    CXGdiObjectSelector(
const CXGdiObjectSelector&);
    
void operator = CXGdiObjectSelector(const CXGdiObjectSelector&);
    
enum {__nGDI_SIZE = 5};
    
enum {XFLAG_PEN=1, XFLAG_BRUSH=2, XFLAG_FONT=4, XFLAG_BITMAP=8, XFLAG_REGION=16};
    HDC  m_hDC;     
    HGDIOBJ m_hOldGdis[__nGDI_SIZE];
    WORD m_nSelectedFlags;
    WORD m_nCreatedFlags;
}
;

        整个类的定义的还是很直观。只是那一组创建GDI对象的成员函数,显得有点格格不入,根据单一职责原则,实在不应该加入这些东西,但是,加入这些操作,确实会给使用的时候带来极大的方便。至于禁用了拷贝和赋值函数,感觉有点多此一举,但是为了满足某些C++洁癖者的强迫症,我还是做了妥协。这个类其他代码的实现,相当简单,我就不赘述了。

        公道自在人心,这个类在选入选出GDI对象,毫无疑问,确实方便安全,但是它以牺牲执行效率和空间为代价。代码编写,不外乎是在做各种各样的权衡,有时付出类型安全,以换取更大的灵活性;有时又以付出灵活性,以换取类型的安全;有时以通用性换取效率;有时又要以效率换取通用。不能简单地说这种权衡好不好,只能说更加合适而已,在某一处上,比较强调什么,就以牺牲其他的特性来得到,谨记80%20%的原则。C++的深入人心,在于它不剥夺程序员选择的权利,同时它又提供了丰富的特性,以供你做各种各样的交换。通用、灵活、效率、安全,这四者总是不可协调的矛盾。MFC框架的最大错误就在于:牺牲了很大很大的灵活、效率、通用,最后只获得了一点点类型安全,这无疑是失败的交换。

posted on 2012-06-01 10:56 华夏之火 阅读(1219) 评论(5)  编辑 收藏 引用

评论

# re: 类设计一则,GDI对象选入器[未登录] 2012-06-01 18:35 春秋十二月

GDI对象有对应的MFC类,选择器选择新的对象,增加一个方法reset,功能是选回老对象,以在任何时机可以调用,而在析构函数实现中调用reset即可。  回复  更多评论   

# re: 类设计一则,GDI对象选入器 2012-06-01 18:42 华夏之火

不希望代码只限于MFC中。关于reset的方法,之前也考虑过,但觉得没有太多的必要,现在类中多增加一个可有可无的方法,都觉得很难受。@春秋十二月
  回复  更多评论   

# re: 类设计一则,GDI对象选入器 2012-06-01 21:06 春秋十二月

呵呵,你的类接口是句柄就行,在win32中一样可用,反正我觉得你的选择类创建GDI对象,那是不明智的做法。  回复  更多评论   

# re: 类设计一则,GDI对象选入器 2012-06-01 21:08 春秋十二月

不用某个方法,不等于不存在这个需求和必要性。随着时间推移,当改进完善你的类时,就会作出变化的。  回复  更多评论   

# re: 类设计一则,GDI对象选入器 2012-06-01 23:05 华夏之火

确实创建GDI对象,是有点不明智,但使用起来,确实很方便,它是创建了GDI对象之后,就选入DC中,最后析构函数中或者再选入新的对象,会被选出来,然后给予删除。至于那个reset,你说的也有道理,但是原本的职责中,本来就不想给用户提供reset的机会@春秋十二月
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(6)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜