65.9K
CodeProject 正在变化。 阅读更多。
Home

基于策略设计的通用 CUpdateUI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (5投票s)

2002年1月5日

4分钟阅读

viewsIcon

75280

downloadIcon

448

一种基于策略的 UI 更新实现,用于累积和执行 UI 更改。

引言

类型列表是 C++ 中最令人惊叹的事物之一,非常奇特!这就是为什么我更喜欢 C++ 而不是任何其他语言。你熟悉类型列表吗?如果不熟悉,那么你必须点击这里 [1] 阅读。Andrei 会更好地向你解释,你绝不会后悔。我已彻底改造了使用类型列表的旧版本,并对其简化了许多事情感到兴奋。

WTL 中有一个名为 CUpdateUI 的类模板,用于管理 UI 元素的更新,我很喜欢它。在大多数情况下,这就是我们需要的。让我思考另一种实现的因素是 CUIUpdate 可以扩展以获得更多功能的方面。你可以选择继承 CUpdateUICUpdateUIBase。这似乎会很混乱和笨拙。我认为拥有一个基于策略设计的 CUpdateUI 通用版本会更好。这种模型的好处是

  • 它可扩展;要添加新功能,我们永远不必完全重写它; 
  • 它可调整;通过在策略集中放置另一个策略,我们可以获得更多功能; 
  • 它具有高性能。 

我尝试过这样做。

实现

我认为它看起来是这样的

class CMainFrame :
    public CFrameWindowImpl<CMainFrame>,
    public CUIStates<StatePolicy1, StatePolicy2, ...>,
    public CUIUpdate<CMainFrame, UIPolicy1, UIPolicy2, ...>
{
    ...
};

CUIStates 是一个类模板,用于累积 CUIUpdate 需要应用的 UI 更改。每个 StatePolicyNs 支持特定类型的更改。 CUIUpdate 显示 CUIStates 中所做的更改。它拥有一组 UIPolicyNs;每个都支持特定类型的用户界面。 CUIStates CUIUpdate 都可以拥有任意数量的策略。你可以指定多达 32 个策略。如果你查看 CUIStates 的定义,你会看到类似这样的内容

struct nil {}

template
<
	typename S01 = nil, typename S02 = nil, typename S03 = nil,
	typename S04 = nil, typename S05 = nil, 
	...
	typename S31 = nil, typename S32 = nil
> 
class CUIStates : 
	...

最后的省略号 (...) 是什么?它是一种派生 CUIStates 的东西,其参数 SXX 非空。这就是类型列表工作的地方,它是一种魔法。这可能是另一篇文章的主题。嗯,编译器有很多工作要做,以便在运行时排除未使用的代码。CUIUpdate 也是以同样的方式完成的。

这是 WTL 向导等效代码,用于更新“Toolbar”和“Status Bar”菜单项

class CMainFrame : 
    public CUIStates<CStyle>,
    public CUIUpdate<CMainFrame, CUICommand<CMainFrame> >,
    ...
{
    ...
    virtual BOOL OnIdle()     {
        UIUpdate();    // CUIUpdate method
        return FALSE;
    }

    BEGIN_MSG_MAP(CMainFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)

        // Does the same as CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
        // Updates popup menus and checks accelerators 
        CHAIN_MSG_MAP(CUICommand<CMainFrame>)

        COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
        COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
        ... 
    END_MSG_MAP()

    LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, 
                     BOOL& /*bHandled*/) {
        ...

        // CStyle methods
        UIAddStyle(ID_VIEW_TOOLBAR, CStyle::Enable|CStyle::Visible|CStyle::Checked);
        UIAddStyle(ID_VIEW_STATUS_BAR, CStyle::Enable|CStyle::Visible|CStyle::Checked);

        ...
        return 0;
    }
    LRESULT OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/, 
                          HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
        bool bVisible = !::IsWindowVisible(m_hWndToolBar);
        ::ShowWindow(m_hWndToolBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);
        UISetCheck(ID_VIEW_TOOLBAR, bVisible);    // CStyle method
        UpdateLayout();
        return 0;
    }
    LRESULT OnViewStatusBar(WORD /*wNotifyCode*/, WORD /*wID*/, 
                            HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
        bool bVisible = !::IsWindowVisible(m_hWndStatusBar);
        ::ShowWindow(m_hWndStatusBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);
        UISetCheck(ID_VIEW_STATUS_BAR, bVisible);    // CStyle method
        UpdateLayout();
        return 0;
    }
    ...
};

CStyle 策略支持命令 ID 的标志,如启用/禁用、可见/隐藏、选中/未选中。 CUICommand 支持弹出菜单的更新并处理加速键。因此, CUICommand 要求 CUIState 具有 CStyle;但如果没有会怎样?没事, CUICommand 只是不做任何事情,我向你保证 CUICommand 没有像这样丑陋的代码

if(Does_CUIState_Have_CStyle()) {
	// translates accelerators
}
else {
	// does nothing
}

所有的工作都是由编译器在编译时完成的,没有任何“if”。你认为这是不对的吗?那么你就来错地方了,请关闭你的浏览器。

如果我们想管理项目的文本标签,我们应该添加一个 CText 状态策略,并使用它的方法来控制 UI 更改。

class CMainFrame :
public CUIStates<CStyle, CText>,
public CUIUpdate<CMainFrame, CUICommand<CMainFrame> >,
...

要管理工具栏按钮,只需重写

class CMainFrame :
public CUIStates<CStyle, CText>,
public CUIUpdate<CMainFrame, CUICommand<CMainFrame>, CUIToolBar<CMainFrame> >,
...

管理状态栏:CUIStatusBar,重吧:CUIReBar

如果你有自己的控件,你可以实现 CUIUpdate 策略,从而将控件添加到该模型中。此外,我们可以指定自己的状态策略,并扩大现有 CUIToolBarCUIStatusBar 的功能。

如何添加新的状态和 UI 策略

示例项目演示了如何实现名为 CParts 的状态策略,该策略管理状态栏部分的数量和宽度,以及如何扩展 CUIStatusBar 以根据 CParts 的变化来更改状态栏。

状态策略应该有三个函数

bool dirty(ui_type id) const
void clean(ui_type id)
void set_default_value()

dirty 和 clean 支持 dirty 标志;与 WTL 的 CUpdateUI 相同。set_default_value 将状态的值设置为默认值(如果状态有的话);这使得 MDI 界面支持更加容易。这足以与 CUIStates 编译。其他方法由你自行决定,它们将在 UI 元素更改应用代码时使用。

UI 策略的要求是它应该有一个以下模板化函数

template<class State>
void update_state(HWND hwnd, const State& state) {}

因此,如果 UI 策略不支持指定类型的状态策略,那么它什么也不做;如果我们希望它支持,例如,CText,那么它应该有如下函数

void update_state(HWND hwnd, CText& state) {
...
}

有关详细信息,您应该查看源代码。

关于效率的几句话。 CUIStates CUIUpdate 都通过积极使用泛型编程技术来实现;它们没有虚函数。因此性能非常高。相比之下,WTL 向导生成的二进制文件的发布大小是相同的。有两种实现方式,在 #include <map> 之前 #include <wtlx/ui_update.hpp>,我们将从 CSimpleMapsCStrings 切换到 STL 库。

我认为使用这些技术将造就 C++ 库的未来。WTL 将会是什么?让我们拭目以待,我只希望它不是 MFC2。为什么会有 CString 这样的东西?是为了让 MFC 程序员感到舒适吗?好吧,好吧,好吧...

致谢

[1] Andrei Alexandrescu. Generic<Programming>: Typelists and Applications

[2] Andrei Alexandrescu. Generic<Programming>: Mappings between Types and Values.

© . All rights reserved.