界面换肤软件学习笔记

本文介绍了使用动态库实现界面换肤的技术细节,包括动态载入DLL并获取函数、在DLL中载入位图资源的方法,以及如何通过主应用程序调用DLL中的函数。此外,还讨论了菜单默认位置、自绘响应函数、消息响应函数等功能。

动态库实现界面换肤

一:动态载入DLL并获取DLL中函数

//在全局区创建一个函数指针
typedef void (_stdcall* funShowDlg)();

//获取动态库,LoadLibrary为动态库文件的存放位置
    HMODULE hMod=LoadLibrary("../SkinDll/Debug/SkinDll.dll");
    funShowDlg ShowDlg;
    if(hMod)
    {
        //获取动态链接库中ShowDlg函数的地址
        ShowDlg=(funShowDlg)GetProcAddress(hMod,_T("ShowDlg"));
        if(ShowDlg)
            ShowDlg();
        FreeLibrary(hMod);
    }

二:在动态链接库中载入位图

//AfxGetResourceHandle用于获取当前资源模块句柄,
//而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。
//为了程序能够访问动态链接库中的位图资源,动态库需要使用静态连接MFC

HBITMAP GetBmpResourceFromDll()
{
    return LoadBitmap(AfxGetResourceHandle,MAKEINTRESOURCE(IDB_BACKGROUND));
    //或者使用以下这句
    //  m_pParts[i].m_hBitmap = //LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(1000+i));
}

处理窗口的WM_CTLCOLOR消息,如果已经加载皮肤文件,则从皮肤文件中加载窗口背景位图,作为窗口的背景位图

CWnd::OnCtlColor
afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
//返回值:OnCtlColor必须返回一个刷子句柄,该刷子将被用于画出控件的背景。

三:动态库使用
在使用动态库方式加载皮肤文件时候,在主应用程序中如果需要用到DLL中的某个函数,则主应用程序应当存在一个与动态库中需要使用的函数同名的一个纯虚函数(注意主应用程序中是纯虚函数,DLL中的同名函数是虚函数),除此之外,主应用程序中的所有纯虚函数的排列顺序必须与DLL中的虚函数的顺序一致(目的是使虚函数表一致),如果虚函数表不一致,在主应用程序中调用DLL中的函数的时候就会调用到错误的函数甚至访问到不可访问的空间而使程序当掉,有一点还需要注意,虚析构函数一会存在与虚函数表中,也需要注意它的排列顺序,非虚函数和变量则无需考虑顺序,实例如下

//主应用程序里的类
class CSkinManage  
{
protected:
    UINT m_PartCount;    //窗体由几部分组成
    /******************窗体各部分位图资源********************
    0,1,2           分别为标题栏的左\中\右3部分
    3,4,5           分别为左,下,右边框
    6,7,8,9,10,11   为标题栏普通按钮和热点按钮
    *********************************************************/
    CFormPart* m_pParts;


public:
    //加载位图资源 
    virtual HBITMAP GetBitmapRes(UINT Index) = 0;
    virtual Release() = 0;
    virtual CPoint GetButtonPos(UINT Index) = 0;
    virtual BOOL GetDrawRound() = 0;
    virtual COLORREF GetMenuBKColor() = 0;
    virtual COLORREF GetMenuSelColor() = 0;

public:
    CSkinManage();
    virtual ~CSkinManage();
};


//DLL中的对应类
class CSkin
{
protected:
    UINT m_PartCount;  //窗体由几部分组成
    /******************窗体各部分位图资源索引: *******************

    0,1,2:         分别为标题栏的左\中\右3部分
    3,4,5:         分别为左,下,右边框
    6,7,8,9,10,11: 为标题栏普通按钮和热点按钮
    12,13:         表示左下角和右下角位图
    14:            表示背景位图
    *************************************************************/
    CFormPart* m_pParts;
    COLORREF m_MenuBkColor;     //菜单背景颜色
    COLORREF m_MenuSelColor;    //菜单选中时的颜色
    BOOL DrawRound;             //是否绘制圆角

public:


    //获取位图资源
    virtual HBITMAP GetBitmapRes(UINT Index)
    {
        return m_pParts[Index].m_hBitmap;
    }
    //释放对象
    virtual Release()
    {   
        delete this;
    }
    //获取按钮的位置
    virtual CPoint GetButtonPos(UINT Index)
    {
        return  m_pParts[Index].m_Pos;
    }

    virtual BOOL GetDrawRound()
    {
        return DrawRound;
    }
    virtual COLORREF GetMenuBKColor()
    {
        return m_MenuBkColor;
    }
    virtual COLORREF GetMenuSelColor()
    {
        return m_MenuSelColor;
    }

    void LoadBitmapRes();       //加载位图资源
    void SetButtonPos() ;       //设置标题栏按钮的相对位置
public:
    CSkin();
    CSkin(UINT PartCount);
    ~CSkin();

};

如果把virtual ~CSkinManage();这一句移动到 virtual HBITMAP GetBitmapRes(UINT Index) = 0;这一句前面,整个程序就会当掉,因为你在调用GetBitmapRes(UINT Index)这个函数的时候其实调用的是~CSkinManage()这个函数,结果显然意见

四:菜单默认位置
当窗口Style属性为Resizing时,菜单的左边界肯定为4(在不人为改变菜单位置的情况下);
当窗口Style属性为Dialog Frame时,菜单的左边界肯定为3(在不人为改变菜单位置的情况下)

五:自绘响应函数
OnMeasureItem(..)函数用于设置自绘项的尺寸
OnDrawItem(…)函数用于根据前面得到的尺寸绘制需要自绘的项目

六:消息响应函数OnSysCommand(OnSysCommand(UINT nID, LPARAM lParam)

void CMynetsendDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);

解释:对话框的系统菜单命令,包括关闭对话框,最小化最大化,弹出关于对话框等等,实际上就是向这个对话框发送WM_SYSCOMMAND消息,对话框响应WM_SYSCOMMAND消息,然后根据不同的nID值判断到底是什么系统命令(关闭对话框,最大最小化还是其他什么),这个OnSysCommand
就是用来响应WM_SYSCOMMAND消息,进行相应处理的。
上面的if ((nID & 0xFFF0) == IDM_ABOUTBOX),就是说在需要弹出关于对话框的时候,就进行特别处理(因为在MFC自动生成的基于对话框的工程里面,这个关于对话框也是用户自己可以控制的),所以这时候就自己处理,弹出自己的关于对话框,对于其他的消息,默认不需要用户定制,就直接调用父类的处理就好

七:OnNcCalcSize()消息响应函数
功能:OnNcCalcSize改变标题栏等的高度
在创建窗口时,当收到 WM_NCCALCSIZE 消息时才指定客户区。不管什么时候,只要 Windows 想知道窗口客户区的大小,它便会发送这个消息。
NCCALCSIZE_PARAMS 结构保存三个矩形数组,第一个保存窗口的客户区。
如果改写主窗口的 WM_NCCALCSIZE/OnNcCalcSize,一定要确保调用基类的默认窗口处理例程,以便实现缺省处理。这样程序一运行便会有得到默认的客户区矩形,然后你可以调整其大小。同样,还应该在OnNcPaint/WM_NCPAINT 中调用基类默认的处理过程。否则 Windows 不会绘制边界,滚动栏或其它标准非客户区元素。如果你实现自己的窗口类,像定制工具栏或调色板,其中要计算客户区矩形并进行绘制处理,你可以不必调用基类默认的窗口过程。随便哪种方法,当窗口收到 WM_NCPAINT 消息时,你都得负责绘制整个非客户区。

八:消息响应函数CalcWindowRect(LPRECT lpClientRect, UINT nAdjustType)
每当主框架窗口的客户区尺寸发生变化或控制条的位置发生变化,需要重新排列客户区时,调用该函数,根据视图客户区尺寸计算视图窗口的尺寸。
我们知道,排列主窗口客户区是由CFrameWnd::RecalcLayout()完成的。显然,视图的CalcWindowRect()函数也是由它触发调用的。主窗口的客户区尺寸减掉所有控制占用的部分,剩下的区域分给视图,这部分区域作为实参传入CalcWindowRect()。在CalcWindowRect()函数内,需要计算视图窗口的尺寸。代码如下:

void CView::CalcWindowRect(LPRECT lpClientRect, UNIT nAdjustType)
  {
  // lpClientRect此时是整个视图客户区的尺寸
  // 需要为滚动条增加尺寸吗
  if (nAdjustType != 0)
  {
  // 调用API,根据窗口风格计算窗口尺寸
  ::AdjustWindowRectEx(lpClientRect, 0, FALSE, GetExStyle());
  DWORD dwStyle = GetStyle();
  if (dwStyle & WS_VSCROLL)
  {
      // 为垂直滚动条增加尺寸
      int nAdjust = afxData.csVScroll;
      if (dwStyle & WS_BORDER)
      nAdjust -= CX_BORDER;
      lpClientRect->right += nAdjust;
  }
  if (dwStyle & WS_HSCROLL)
  {
      // 为水平滚动条增加尺寸
      int nAdjust = afxData.cyHScroll;
      if (dwStyle & WS_BORDER)
      nAdjust -= CY_BORDER;
      lpClientRect->bottom += nAdjust;

      }
        return;
  }

  // 无需为滚动条增加尺寸,调用基类成员完成计算
  CWnd::CalcWindowRect(lpClientRect, nAdjustType);
  }

组件库实现界面换肤
一:OnCtrlColor(HWND hWnd)
如果在某个函数中使用了CWindowDC或者CClientdc这两种画图设备,则无需人工释放(xxx->DeleteDC()),这连个类的析构函数会自动释放创建的画图设备,如果人工释放,会因重复释放而使程序当掉

二:WM_CTRCOLOR
在组件库中需要自己修改窗口过程,然后在窗口过程中有些时候需要使用到WM_CtrlColor消息,笔者在使用的过程中发现单纯的使用WM_CtrlColor消息,则自定义窗口过程函数不会响应这个消息,需要使用WM_CtrlColorDlg或者WM_CtrlColorEdit或者WM_CtrlColorButton,因为笔者在组件库中分别为三种对象修改了窗口过程,单纯使用WM_CtrlColor不会被响应,需要加上类别

------------------界面预览---------------------------- ------------------压缩包文件---------------------------- ------------------简略说明---------------------------- [+] 皮肤按钮换肤。 [+] 关闭按钮关闭。 [+] 图标选择夹,未做系统组件识别。 [+] 文字选择夹,同上。 [+] 仿酷狗虚表未完成,以下功能已完成。 1.分组展开,收缩。 2.分组,表项热点追踪。 3.表项选中,播放状态。 4.表项滚动。 以下功能未完成,备注思路。 1.关于热点小按钮位置,自绘部分已完成;内部组件部分,可以在初始化创建,热点中显示或隐藏。 2.关于热点小按钮事件。 自绘部分:添加成员变量记录矩形,用于命中判断,实现单击或右击。 内部组件:已完成,屏蔽了。 3.关于分组,表项事件,添加方法,类回调。可参考源码。 交流部分: [1] 2.0 部分 缺点:消息机制不完善,绘制接口简单,扩展性差。CPU占用高。不支持D2D渲染,没有皮肤和多语言接口等。 优点:逻辑方式形同 易语言 ,学习成本低。类继承层次少,内存使用少,易于修改,扩展接口功能。执行效率高。 [2] 3.0 部分 缺点:不支持布局,带有DLL,执行效率较低(相比2.0 4.1)。命令完善但是很多BUG,经常写着写着就要去找BUG,DLL没开源,还好本人遇到的BUG都可以在模块里面修复。 优点:例程多,组件全。逻辑方式形同易语言,学习成本低。相较2.0 增加XML布局,增加皮肤接口等。 [3] 4.1 部分 缺点:带有DLL(已开源),GDI 渲染BUG较多,例程少,组件开放性高(用易语言来开发界面,代码量增加),逻辑方式不同于易语言,全英文调用,消息传参,内存指针回调,上手难度较高。 优点:执行效率较高,占用资源少,支持GPU加速。增加丰富的布局接口。支持XML,皮肤接口,D2D渲染等。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值