界面换肤软件学习笔记

动态库实现界面换肤

一:动态载入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不会被响应,需要加上类别

猜你喜欢

转载自blog.csdn.net/chenyonken/article/details/78115657