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

C++ 中的 Windows 开发,使用菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (59投票s)

2012 年 10 月 6 日

CPOL

19分钟阅读

viewsIcon

182831

downloadIcon

4160

Windows API、菜单、C++ Lambda 表达式、std::enable_shared_from_this

引言

在本文中,我们将继续探索使用 Visual C++ 2012 进行 Windows C++ 开发。我们将介绍一组 C++ 类,它们使我们能够相对轻松地以编程方式实现菜单。

本系列的目的是阐明一种更安全、更容易的 C++ 开发方法,这种方法由 C++11 标准实现,同时直接在基于 Windows C 和 COM 的 API 之上构建代码,实现了一个提供更简单编程模型的库。尽管附带的库仍在开发中,但它现在包含超过 450 个可用类——并且还在稳步增长。

部分库功能

  • Windows API 错误转换为异常
  • 适当时将 COM HRESULT 映射到异常
  • Direct2D 包装类
  • DirectWrite 包装类
  • Windows 图像组件包装类
  • Windows 属性系统包装类
  • 核心 COM 实用程序和接口包装类
  • 广泛使用 std::shared_ptr<> 防止资源泄漏
  • GDI 位图、图标、字体、画刷、画笔和设备上下文类
  • StringDateTimeTimeSpan 和高分辨率 Stopwatch
  • 用户界面控件正在开发中
  • 线性代数:大型稀疏非对称线性方程组的并行求解器

仍然有很多工作要做,但反馈,以及偶尔的鼓励,将不胜感激。

由于库将错误转换为异常,我们能够专注于手头的开发任务

HWIN_EXPORT std::shared_ptr<BitmapHandle> BitmapHandle::LoadFromFile(const String& theFileMame)
{
  auto factory = ImagingFactory::Create();
  auto decoder = factory.CreateDecoderFromFilename(theFileMame);
  auto frame = decoder.GetFrame(0);
  auto result = frame.AsBitmapHandle();
  return result;
}

本系列始于使用 Direct2D & DirectWrite 渲染文本[^]。

Windows API 提供了两个函数来创建菜单

HMENU CreateMenu();
HMENU CreatePopupMenu();

这些函数返回一个 HMENU 值,这是一个句柄,只是一个数字标识符,用于其中一个函数创建的菜单。句柄表示 Windows 代表我们的应用程序分配的资源,因此当不再需要句柄时,将这些资源释放回操作系统非常重要。我们使用

BOOL DestroyMenu(HMENU hMenu);

Windows API 函数释放以编程方式创建的菜单句柄。

我们通常认为是菜单栏的是一个菜单,而每个下拉菜单或子菜单实际上是带有各自句柄的独立菜单,因此我们需要一个适当的机制来管理菜单层次结构所需的所有句柄。

动机

下面的代码展示了我想如何使用这些类来定义一个简单的菜单结构。我们应该能够使用单个语句创建菜单项,并且应该很容易定义当用户从菜单中选择该项时我们希望发生什么。

void MyForm::InitializeMenuBar()
{
    auto self = As<MyForm>();
    auto fileNewMenuItem = make_component<TextMenuItem>(self,L"&New");
    auto fileOpenMenuItem = make_component<TextMenuItem>(self,L"&Open");
    auto fileSaveMenuItem = make_component<TextMenuItem>(self,L"&Save");
    auto fileSeparator = make_component<SeparatorMenuItem>(self);
    auto fileExitMenuItem = make_component<TextMenuItem>(self,L"E&xit");

    fileNewMenuItem->OnClick.connect([&](MenuItem*)
            { text = L"New selected"; InvalidateRect(); });
    fileOpenMenuItem->OnClick.connect([&](MenuItem*)
            { text = L"Open selected"; InvalidateRect(); });
    fileSaveMenuItem->OnClick.connect([&](MenuItem*)
            { text = L"Save selected"; InvalidateRect(); });
    fileExitMenuItem->OnClick.connect([&](MenuItem*){ Close(); });

    auto fileSubMenu = make_component<SubMenuItem>(self,L"&File");

    fileSubMenu->Add(fileNewMenuItem);
    fileSubMenu->Add(fileOpenMenuItem);
    fileSubMenu->Add(fileSaveMenuItem);
    fileSubMenu->Add(fileSeparator);
    fileSubMenu->Add(fileExitMenuItem);

    auto editSubMenu = make_component<SubMenuItem>(self,L"&Edit");
    
    auto editCutMenuItem = editSubMenu->AddMenuItem(L"Cu&t");
    auto editCopyMenuItem = editSubMenu->AddMenuItem(L"&Copy");
    auto editPasteMenuItem = editSubMenu->AddMenuItem(L"&Paste");
    editCutMenuItem->OnClick.connect([&](MenuItem*)
           { text = L"Cut selected"; InvalidateRect(); });
    editCopyMenuItem->OnClick.connect([&](MenuItem*)
           { text = L"Copy selected"; InvalidateRect(); });
    editPasteMenuItem->OnClick.connect([&](MenuItem*)
           { text = L"Paste selected"; InvalidateRect(); });

    auto viewSubMenu = make_component<SubMenuItem>(self,L"&View");
    auto viewTime = viewSubMenu->AddMenuItem(L"&Time");
    viewTime->OnClick.connect([&](MenuItem*)
    {   
        DateTime now = DateTime::Now();
        if(now.IsDaylightSavingTime())
        {
            text = now.ToString() + L" Daylight saving time"; 
        }
        else
        {
            text = now.ToString() + L" Standard time"; 
        }
        InvalidateRect(); 
    });

    auto menuBar = make_component<MenuBar>(self);

    menuBar->Add(fileSubMenu);
    menuBar->Add(editSubMenu);
    menuBar->Add(viewSubMenu);

    SetMenu(menuBar);
}

首先,我们定义构成“文件”子菜单元素的五个对象。“新建”、“打开”、“保存”和“关闭”是 TextMenuItem 对象,我们还创建了一个 SeparatorMenuItem,它指定了将“关闭”与其他项分开的细线。

然后我们使用 C++ lambda 表达式定义当用户选择不同的菜单项时我们希望程序执行的操作,然后我们创建“文件SubMenuItem 并按照我们希望它们在菜单中出现的顺序添加菜单项。

编辑”子菜单使用稍微不同的方法创建,首先创建“编辑SubMenuItem,这允许我们直接将菜单项添加到菜单中。

水平的顶级菜单由 MenuBar 对象表示,我们将三个子菜单添加到 MenuBar 对象,然后使用 SetMenu 将整个菜单结构添加到我们的窗体。

外部库

该代码依赖于 Boost C++ 库,可以从https://boost.ac.cn[^] 下载,因此您需要下载并构建它们,然后使用与您的安装匹配的包含和库路径更新提供的项目。

实现

这最初是对 std::shared_ptr 的实验,智能指针模板现在是 C++ 标准库的一部分。如果您要推荐一项技术,您应该能够根据您的建议构建一些有用的东西。

C++ 智能指针不是什么新东西,它们以各种形式存在的时间几乎与 C++ 一样长;它们是一个非常有用的构造。

因此,作为 std::shared_ptr 有用性的一个实际例子,我正在构建一个用于用户界面开发的 C++ 库,现在我开始真正了解我的方向。

这似乎是一个可行的设计,在某种程度上基于 .NET,同时考虑了我认为是其前身的元素:Embarcadero 的 VCL;甚至 IBM 的用户界面类库。

该库仍处于非常早期的设计阶段,但我仍然认为它可能具有一定的兴趣——特别是对于那些对 std::shared_ptr 的有用性感到疑惑的人。

句柄的概念非常普遍,因此有一个 Handle 类作为各种类型句柄的基类是有意义的。Handle 类有一个虚析构函数,因此即使在指向 Handle 对象的指针或引用上调用析构函数,我们也能够实现将使用正确的 Windows API 函数销毁资源的类型。

虽然我们可以在一个类中实现菜单项所需的功能,但我认为有一个 MenuItem 基类,然后实现表示菜单项各种预期用途的特定类型是有意义的。我认为这使得使用这些类的代码更具可读性。

在 Windows API 中,窗口通过为特定窗口类注册到操作系统的用户定义回调过程接收通知,菜单事件通知通过这种机制工作。该库提供了一种将这些事件通知路由到...

HWIN_EXPORT virtual void HandleMessage(Message& message);

...在 Control 类中声明的函数。该函数以对 Message 对象的引用作为其参数,该对象表示从 Windows 发送到窗口的通知,并将消息路由到各自的处理程序函数,例如 WM_MENUCOMMAND 消息将被路由到...

HWIN_EXPORT virtual void DoOnMenuCommand(Message& message);

...函数。Message 类派生自 Windows tagMSG 结构...

typedef struct tagMSG {
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

...并提供两个附加字段...

class Message : public tagMSG
{
public:
    typedef LRESULT Result;

    Result result;
    bool handled;

    Message()
        : result(0),
            handled(false)
    {
        memset(&hwnd,0,sizeof(tagMSG));
    }
};

... result 允许处理程序函数设置将从回调返回给 Windows 的值,只要 handled 设置为 true。默认情况下,handled 设置为 false,导致消息传递给 DefWindowProc 函数,该函数执行默认操作并返回消息结果。

Form 类,表示顶级窗口,派生自 Control 类——并添加了顶级窗口特有的功能。

该类使用 std::shared_ptr<menubar> menuBar 成员,它维护对当前菜单栏的引用,可以使用 SetMenu 函数设置,并使用 GetMenu 函数检索。

class Form : public ContainerControl
{
       .
       .

    std::shared_ptr<MenuBar> menuBar; 
       .
       .
public:
       .
       .
    HWIN_EXPORT std::shared_ptr<MenuBar> GetMenu() const;
    HWIN_EXPORT Form& SetMenu(const std::shared_ptr<MenuBar>& theMenu);
       .
       .
};

现在我们已经确定了通知如何从窗口传递到我们的应用程序,是时候看看我们如何使用 Windows API 来实现菜单以及如何将应用程序接收到的 WM_MENUCOMMAND 事件路由到我们的 MenuItem 实现的 OnClick boost::signals2::signal<void>。

我们将首先看看如何实现菜单栏的构造函数

HWIN_EXPORT std::shared_ptr<MenuHandle> MenuBar::CreateHandle()
{
    HMENU hMenu = ::CreateMenu();
    if(!hMenu)
    {
        ThrowLastOSError();
    }
    auto result = std::make_shared<MenuHandle>(hMenu,true);
    return result;
}

HWIN_EXPORT MenuBar::MenuBar()
    : Base(CreateHandle())
{
    AddStyle(MNS_NOTIFYBYPOS);
}

MenuBar 类的 static CreateHandle 函数由构造函数调用,并将有效的菜单句柄传递给基类;或者抛出包含从 Windows 检索到的关于阻止 Windows 创建该句柄的错误的 std::exception

然后我们调用 AddStyleMNS_NOTIFYBYPOS 添加到新创建菜单的样式中。

HWIN_EXPORT Menu& Menu::AddStyle(DWORD style)
{
    MENUINFO menuinfo = {sizeof(menuinfo),0};
    menuinfo.fMask = MIM_STYLE;
    GetMenuInfo(menuinfo);
    menuinfo.dwStyle |= style;
    SetMenuInfo(menuinfo);

    return *this;
}

MNS_NOTIFYBYPOS 样式导致 Windows 在用户进行选择时发送 WM_MENUCOMMAND 事件而不是 WM_COMMAND 事件。WM_MENUCOMMAND 提供的信息通过其句柄和所选项目在该菜单中的偏移量来标识菜单,这更适合我们的实现,因为我们不需要跟踪命令 ID 并将其映射到特定的菜单项。

每个菜单都有一个项目列表,由 Menu 类在其构造函数中创建的 MenuItems 对象管理

HWIN_EXPORT Menu::Menu(std::shared_ptr<MenuHandle> menuHandle)
    : Base( ), 
        handle( menuHandle )
{
    if(menuHandle)
    {
        HMENU hMenu = menuHandle->GetHMENU();
        if(hMenu)
        {
            AddToMenuMap(hMenu,this);
        }
    }
    items = std::make_shared<MenuItems>(this);
}

std::enable_shared_from_this<T>

std::enable_shared_from_this 提供了一种机制,允许当前由 std::shared_ptr ptr0 管理的对象 obj 安全地生成额外的 std::shared_ptr 实例 ptr1ptr2 – 所有这些都与 ptr0 共享 obj 的所有权。

std::enable_shared_from_this<T> 继承为类型 T 提供了一个成员函数 shared_from_this。当类型 T 的对象 obj 由名为 ptrstd::shared_ptr<T> 管理时,调用 T::shared_from_this 将返回一个新的 std::shared_ptr<T>,它与 ptr 共享 obj 的所有权。

obj 上调用 shared_from_this 之前,必须存在拥有 obj 的现有 std::shared_ptr 因此,您不能在构造函数内部调用 shared_from_this 来获取正在构造的对象的 std::shared_ptr

请注意,您应该使用 shared_from_this 代替 std::shared_ptr<T>(this) 之类的表达式——因为后者很可能导致 this 被多个彼此不知情的拥有者多次销毁。

在派生自 Object 的类的情况下,shared_from_this 返回一个 std::shared_ptr<Object> 对象,我们通常希望将其转换为 std::shared_ptr<T>,其中 T 是派生自 Object 的类型。

Object 类提供了一些辅助模板函数,可以简化向下转型,这对于 .NET 开发人员来说应该有些熟悉

template<typename T> 
std::shared_ptr<const T> As() const
{
    return std::dynamic_pointer_cast<const T,const Object>(shared_from_this());
}

template<typename T> 
std::shared_ptr<T> As()
{
    return std::dynamic_pointer_cast<T,Object>(shared_from_this());
}

template<typename T> 
bool Is() const
{
    auto downcasted = As<T>();
    if(downcasted)
    {
        return true;
    }
    return false;
}

std::dynamic_pointer_cast<T,Object>(shared_from_this()) 将返回 std::shared_ptr<T> 的新实例,其中包含从 shared_from_this() 返回的 std::shared_ptr<Object> 转换后的管理对象类型。两个智能指针将共享管理对象的所有权。

菜单项

MenuItems 类是一个简单的 MenuItem 对象容器,它使用 std::shared_ptr<MenuItem> 保存每个 MenuItem 对象。

class MenuItems 
{
public:
    typedef std::vector< std::shared_ptr< MenuItem > > vector;
private:
    friend class Menu;
    Menu* owner;
    vector items;
public:
    HWIN_EXPORT MenuItems(Menu* theOwner);
    HWIN_EXPORT ~MenuItems( );
    HWIN_EXPORT std::shared_ptr< TextMenuItem > AddMenuItem( const wchar_t* theText );
    HWIN_EXPORT std::shared_ptr< TextMenuItem > AddMenuItem( const String& theText );

    HWIN_EXPORT std::shared_ptr< SeparatorMenuItem> AddSeparator();

    HWIN_EXPORT std::shared_ptr< SubMenuItem > AddSubMenu(const wchar_t* theText);
    HWIN_EXPORT std::shared_ptr< SubMenuItem > AddSubMenu(const String& theText);

    HWIN_EXPORT std::shared_ptr< Menu > Menu() const;
    HWIN_EXPORT MenuItems& Add( std::shared_ptr< MenuItem > menuItem);
    HWIN_EXPORT MenuItems& Remove( std::shared_ptr< MenuItem > menuItem);

    HWIN_EXPORT int IndexOf(std::shared_ptr< const MenuItem> menuItem) const;
    HWIN_EXPORT std::shared_ptr< const MenuItem > Item(int position) const;
    HWIN_EXPORT std::shared_ptr< MenuItem > Item(int position);
};

由于 MenuItems 对象是由 Menu 构造函数创建的,我们不能将 std::shared_ptr<Menu> 对象传递给 MenuItems 构造函数,我们需要将原始指针传递给正在构造的 Menu 对象。

Menu() 函数返回一个 std::shared_ptr< Menu >,可以使用 As<T>() 模板函数轻松地从 owner 中检索它。

HWIN_EXPORT std::shared_ptr<Menu> MenuItems::Menu() const
{
    if(owner)
    {
        return owner->As<harlinn::windows::Menu>();
    }
    return std::shared_ptr<harlinn::windows::Menu>();
}

Add 函数设置 parentMenu,它是一个 std::weak_ptr<Menu> 对象,现在通过 ParentMenu 函数向 MenuItem 对象提供对其所属 Menu 的访问。

HWIN_EXPORT MenuItems& MenuItems::Add(std::shared_ptr<MenuItem> menuItem)
{
    if(menuItem)
    {
        auto previousMenu = menuItem->ParentMenu();
        auto thisMenu = Menu();
        if(previousMenu != thisMenu)
        {
            if(previousMenu)
            {
                previousMenu->Items()->Remove(menuItem);
            }
            menuItem->parentMenu = thisMenu;
            items.push_back(menuItem);
            menuItem->DoOnAdd();
        }
    }
    return *this;
}

接下来,我们将 std::shared_ptr<MenuItem> 添加到 items 向量中,这确保了 MenuItem 对象将在此 MenuItems 对象的生命周期内存在,或者直到它们被移除。现在内务工作已完成,我们调用 MenuItem 类的 DoOnAdd() 函数。

ParentMenu() 函数内部,我们使用 lock() 函数将 std::weak_ptr<Menu> 转换为 std::shared_ptr<Menu> 对象。

lock() 函数和 std::shared_ptr 的构造函数都可以用来从 std::weak_ptr 获取管理对象的所有权。不同之处在于,当 std::weak_ptr 参数为空时,std::shared_ptr<T> 的构造函数会抛出异常,而 std::weak_ptr<T>::lock() 会构造一个空的 std::shared_ptr<T>

HWIN_EXPORT std::shared_ptr<Menu> MenuItem::ParentMenu() const
{
    auto theParentMenu = parentMenu.lock();
    return theParentMenu;
}

MenuItem

菜单上的每个项目都由一个 MenuItem 对象表示

class MenuItem : public Component
{
    friend class Control;
    friend class Menu;
    friend class MenuItems;
    std::weak_ptr<Menu> parentMenu;

      .
      .

public:
typedef Component Base;

    HWIN_EXPORT MenuItem( );
    HWIN_EXPORT ~MenuItem( );

    HWIN_EXPORT std::shared_ptr<MenuItems> ParentMenuItems() const;
    HWIN_EXPORT std::shared_ptr<Menu> ParentMenu() const;
    HWIN_EXPORT int IndexOf( ) const;

    HWIN_EXPORT std::shared_ptr<BitmapHandle> Bitmap() const;
    HWIN_EXPORT MenuItem& SetBitmap(std::shared_ptr<BitmapHandle> theBitmap);

    HWIN_EXPORT std::shared_ptr<BitmapHandle> CheckedBitmap() const;
    HWIN_EXPORT MenuItem& SetCheckedBitmap(std::shared_ptr<BitmapHandle> theCheckedBitmap);

    HWIN_EXPORT std::shared_ptr<BitmapHandle> UncheckedBitmap() const;
    HWIN_EXPORT MenuItem& SetUncheckedBitmap
             (std::shared_ptr<BitmapHandle> theUncheckedBitmap);

    HWIN_EXPORT bool IsChecked() const;
    HWIN_EXPORT MenuItem& SetChecked(bool value = true);

    HWIN_EXPORT bool IsDefault() const;
    HWIN_EXPORT MenuItem& SetDefault(bool value = true);

    HWIN_EXPORT bool IsDisabled() const;
    HWIN_EXPORT MenuItem& SetDisabled(bool value = true);

    HWIN_EXPORT bool IsEnabled() const;
    HWIN_EXPORT MenuItem& SetEnabled(bool value = true);
            
    HWIN_EXPORT bool IsGrayed() const;
    HWIN_EXPORT MenuItem& SetGrayed(bool value = true);

    HWIN_EXPORT bool IsHighlighted() const;
    HWIN_EXPORT MenuItem& SetHighlighted(bool value = true);

    boost::signals2::signal<void ( MenuItem* sender )> OnClick;
    boost::signals2::signal<void 
         ( MenuItem* sender, MEASUREITEMSTRUCT& measureItemStruct )> OnMeasureItem;
    boost::signals2::signal<void 
         ( MenuItem* sender, DRAWITEMSTRUCT& drawItemStruct )> OnDrawItem;
protected:
    HWIN_EXPORT virtual MenuItem& DoOnAdd();
    HWIN_EXPORT virtual MenuItem& DoOnRemove();
    HWIN_EXPORT virtual const MenuItem& UpdateMenuItem() const;
    HWIN_EXPORT virtual const MenuItem& InitializeMenuItemInfo(MENUITEMINFO& info) const;
    HWIN_EXPORT virtual void DoOnMenuCommand(Message& message);
    HWIN_EXPORT virtual void DoOnMeasureItem(MEASUREITEMSTRUCT& measureItemStruct);
    HWIN_EXPORT virtual void DoOnDrawItem(DRAWITEMSTRUCT& drawItemStruct);

DoOnAdd() 函数负责将 MenuItem 对象的 Windows 菜单项添加到其所属的菜单中。

HWIN_EXPORT MenuItem& MenuItem::DoOnAdd()
{
    auto menu = ParentMenu();
    if(menu)
    {
        MENUITEMINFO info;
        this->InitializeMenuItemInfo(info);

        auto index = IndexOf();

        menu->GetHandle()->InsertMenuItem(index,true,info);
    }
    return *this;
}

InitializeMenuItemInfo 函数负责初始化 MENUITEMINFO 结构,特别是它将结构的 dwItemData 成员设置为 this 指针。Windows WM_DRAWITEMWM_MEASUREITEM 通知将此值传递回应用程序,允许我们确定我们将绘制或测量的 MenuItem 对象。

WM_MENUCOMMAND、WM_DRAWITEM & WM_MEASUREITEM

Control 类的 HandleMessage 函数基本上是一个 switch 语句,将通知路由到各自的处理程序函数

HWIN_EXPORT void Control::HandleMessage(Message& message)
{
    switch(message.message)
    { 
           .
           .
        case WM_DRAWITEM:
            this->DoOnDrawItem(message);
            break;
           .
           .

        case WM_MEASUREITEM:
            this->DoOnMeasureItem(message);
            break;
        case WM_MENUCOMMAND:
            this->DoOnMenuCommand(message);
            break;
           .
           .

    }
}

当 Windows 需要获取测量信息或为应用程序提供执行自己的所有者绘制元素渲染的能力时,它会向窗口回调函数发送 WM_MEASUREITEMWM_DRAWITEM 通知。由于 InitializeMenuItemInfo 为 Windows 提供了指向 MenuItem 的指针,我们现在可以使用 MEASUREITEMSTRUCTDRAWITEMSTRUCTitemData 成员访问该指针,并根据需要对 MenuItem 对象调用 DoOnMeasureItemDoOnDrawItem

HWIN_EXPORT void Control::DoOnMeasureItem(Message& message)
{
    OnMeasureItem(message);
    if(!message.handled)
    {
        MEASUREITEMSTRUCT* measureItemStruct = (MEASUREITEMSTRUCT*)message.lParam;
        if(measureItemStruct && measureItemStruct->CtlType == ODT_MENU)
        {
            MenuItem* menuItem = (MenuItem*)measureItemStruct->itemData;
            if(menuItem)
            {
                menuItem->DoOnMeasureItem(*measureItemStruct);
                message.handled = true;
                message.result = true;
            }
        }
    }
}

我们使用 CtlType 来确定通知是否用于菜单,将 handled 设置为 true 以防止通知传递给 DefWindowProc,并将 result 设置为 true,这让 Windows 知道我们已经处理了消息。

HWIN_EXPORT void Control::DoOnDrawItem(Message& message)
{
    OnDrawItem(message);
    if(!message.handled)
    {
        DRAWITEMSTRUCT* drawItemStruct = (DRAWITEMSTRUCT*)message.lParam;
        if(drawItemStruct && drawItemStruct->CtlType == ODT_MENU)
        {
            MenuItem* menuItem = (MenuItem*)drawItemStruct->itemData;
            if(menuItem)
            {
                menuItem->DoOnDrawItem(*drawItemStruct);
                message.handled = true;
                message.result = true;
            }
        }
    }
}

WM_MENUCOMMAND 是用户选择菜单上的项目时 Windows 发送的通知,它不提供指向 MenuItem 对象的指针。我们得到的是菜单的句柄和所选菜单项在该菜单上的位置。

HWIN_EXPORT void Control::DoOnMenuCommand(Message& message)
{
    OnMenuCommand(message);
    if(!message.handled)
    {
        HMENU hMenu = (HMENU)message.lParam;
        if(hMenu)
        {
            auto menu = Menu::GetFromMenuMap(hMenu);
            if(menu)
            {
                menu->DoOnMenuCommand(message);
            }
        }
    }
}

Menu 类维护一个菜单句柄到 Menu 对象的内部映射,该映射允许使用 Menu 类的 static GetFromMenuMap 函数轻松访问给定其句柄的 Menu 对象,从而允许 Control 类将通知传递给 Menu 对象。

HWIN_EXPORT void Menu::DoOnMenuCommand(Message& message)
{
    OnMenuCommand(message);
    if(!message.handled)
    {
        int position = message.wParam;
        std::shared_ptr<MenuItem> item = GetItem(position);
        if(item)
        {
            item->DoOnMenuCommand(message);
        }
    }
}

Menu 类使用 Windows 提供的位置信息来获取与该位置对应的 MenuItem 对象,然后在该对象上调用 DoOnMenuCommand

HWIN_EXPORT void MenuItem::DoOnMenuCommand(Message& message)
{
    OnClick( );
}

DoOnMenuCommand 只是在 MenuItem 上引发 OnClick 信号,执行我们之前在 MyForm::InitializeMenuBar() 函数中创建菜单时分配的那些 C++ lambda 表达式。

历史

  • 2012 年 10 月 6 日 - 初次发布
  • 2012 年 10 月 20 日 - 错误修复和库的广泛更改
  • 2012 年 10 月 21 日 - 添加了一些新类
    • Environment::KnownFolder - 包装 SHGetKnownFolderPath 函数
    • Environment::UserName - 包装 GetUserNameEx 函数
    • Environment::ComputerName - 包装 GetComputerNameEx 函数
    • Environment::SystemMetrics - 包装 GetSystemMetrics 函数
    这些类使得从系统检索信息更容易。
  • 2012 年 10 月 23 日 - 添加了一些新类
    • ColorRef
    • BrushHandle
    • PenHandle
    • FontHandle
    • RegionHandle
    • PaletteHandle
    • DeviceContextHandle:文本、线条、形状、位图、变换
    • PaintDeviceContextHandle
    • MemoryDeviceContextHandle
    • BitmapSelection:确保选择到设备上下文中的位图被取消选择
    • BrushSelection:确保选择到设备上下文中的画刷被取消选择
    • PenSelection:确保选择到设备上下文中的画笔被取消选择
    • FontSelection:确保选择到设备上下文中的字体被取消选择
  • 2012 年 10 月 25 日 - 添加了一些新功能
    • BitmapHandle::LoadFromFile - 加载 PNG、JPG、BMP、GIF 图像
    添加了新示例 DeviceContextExample,演示 DeviceContextHandle
    • DrawCaption
    • DrawCaptionFrameControl
    • DrawMenuBarFrameControl
    • DrawScrollBarFrameControl
    • DrawButtonFrameControl
    • DrawEdge
    • DrawFocusRect
    • DrawState
    • DrawDesktopWallPaper
  • 2012 年 10 月 30 日 - 添加了新类
    • Variant - 灵活的 COM VARIANT 包装器,带有一些不错的功能
      • 完整的比较运算符集
      • Addition: Variant a = 2; Variant b = 3; Variant c = a + b;
      • Subtraction: Variant a = 3; Variant b = 2; Variant c = a - b;
      • Multiplication: Variant a = 3; Variant b = 2; Variant c = a * b;
      • Division: Variant a = 3; Variant b = 2; Variant c = a / b;
      • Modulus: Variant a = 3; Variant b = 2; Variant c = a % b;
    • PropertyVariant - COM PROPVARIANT 包装器
    • SysString - COM BSTR 包装器
    • SafeArray - COM SAFEARRAY 包装器
    • Marshal - IMarshal 包装器
    • Malloc - IMalloc 包装器
    • EnumUnknown - IEnumUnknown 包装器
    • SequentialStream - ISequentialStream 包装器
    • Stream - IStream 包装器
    • Persist - IPersist 包装器
    • PersistStream - IPersistStream
    • EnumMoniker - IEnumMoniker 包装器
    • Moniker - IMoniker 包装器
    • RunningObjectTable - IRunningObjectTable 包装器
    • BindCtx - IBindCtx 包装器
    • RunnableObject - IRunnableObject 包装器
    • ROTData - IROTData 包装器
    • EnumSTATSTG - IEnumSTATSTG 包装器
    • Storage - IStorage 包装器
    • PersistFile - IPersistFile 包装器
    • PersistStorage - IPersistStorage 包装器
    • LockBytes - ILockBytes 包装器
    • EnumFORMATETC - IEnumFORMATETC 包装器
    • EnumSTATDATA - IEnumSTATDATA 包装器
    • RootStorage - IRootStorage 包装器
    • AdviseSink - IAdviseSink 包装器
    • AsyncAdviseSink - AsyncIAdviseSink 包装器
    • AdviseSink2 - IAdviseSink2 包装器
    • AsyncAdviseSink2 - AsyncIAdviseSink2 包装器
    • StgMedium - 管理 STGMEDIUM 生命周期
    • DataObject - IDataObject 包装器
    • DataAdviseHolder - IDataAdviseHolder
    • MessageFilter - IMessageFilter 包装器
    • ClassActivator - IClassActivator 包装器
    • FillLockBytes - IFillLockBytes 包装器
    • ProgressNotify - IProgressNotify 包装器
    • LayoutStorage - ILayoutStorage 包装器
    • BlockingLock - IBlockingLock 包装器
    • DirectWriterLock - IDirectWriterLock 包装器
    • ForegroundTransfer - IForegroundTransfer 包装器
    • ProcessLock - IProcessLock 包装器
    • SurrogateService - ISurrogateService 包装器
    • InitializeWithFile - IInitializeWithFile 包装器
    • InitializeWithStream - IInitializeWithStream 包装器
    • PropertyStore - IPropertyStore 包装器
    • NamedPropertyStore - INamedPropertyStore 包装器
    • ObjectWithPropertyKey - IObjectWithPropertyKey 包装器
    • PropertyChange - IPropertyChange 包装器
    • PropertyChangeArray - IPropertyChangeArray 包装器
    • PropertyStoreCapabilities - IPropertyStoreCapabilities 包装器
    • PropertyStoreCache - IPropertyStoreCache 包装器
    • PropertyEnumType - IPropertyEnumType 包装器
    • PropertyEnumType2 - IPropertyEnumType2 包装器
    • PropertyEnumTypeList - IPropertyEnumTypeList 包装器
    • PropertyDescription - IPropertyDescription 包装器
    • PropertyDescriptionList - IPropertyDescriptionList 包装器
    • PropertyDescription2 - IPropertyDescription2 包装器
    • PropertyDescriptionAliasInfo - IPropertyDescriptionAliasInfo 包装器
    • PropertyDescriptionSearchInfo - IPropertyDescriptionSearchInfo 包装器
    • PropertyDescriptionRelatedPropertyInfo - IPropertyDescriptionRelatedPropertyInfo 包装器
    • PropertySystem - IPropertySystem 包装器
    • PropertyStoreFactory - IPropertyStoreFactory 包装器
    • DelayedPropertyStoreFactory - IDelayedPropertyStoreFactory 包装器
    • PersistSerializedPropStorage - IPersistSerializedPropStorage 包装器
    • PersistSerializedPropStorage2 - IPersistSerializedPropStorage2 包装器
    • PropertySystemChangeNotify - IPropertySystemChangeNotify 包装器
    • CreateObject_ - ICreateObject 包装器
    Unknown 类添加了一个新函数
    template<typename T>
     T As() const
    {
    ...
    }
    它提供了一个很好的 QueryInterface 替代方案,假设 unk 是一个 Unknown 对象
    Stream stream = unk.As<Stream>();

    只要包装对象实际支持 IStream 接口,它就会按预期工作,否则 stream 对象将为空。

    if(stream)
    {
      stream.Seek(100,SeekOrigin::StartOfFile);
       .
       .
       .
    }
  • 2012 年 11 月 4 日 - 添加了新类
    • TypeComp - ITypeComp 包装器
    • TypeInfo - ITypeInfo 包装器
    • TypeLib - ITypeLib 包装器
    • Dispatch - IDispatch 包装器
    • ShellItem - IShellItem2 包装器
    • EnumShellItems - IEnumShellItems 包装器
    • ShellItemArray - IShellItemArray 包装器
    • ModalWindow - IModalWindow 包装器
    • FileDialogEvents - IFileDialogEvents 包装器
    • FileDialogEventsImplementation - IFileDialogEvents 实现
    • FileDialog - IFileDialog 包装器
    • FileOpenDialog - IFileOpenDialog 包装器
    • FileSaveDialog - IFileSaveDialog 包装器
    • xml::dom::Implementation - IXMLDOMImplementation 包装器
    • xml::dom::Node - IXMLDOMNode 包装器
    • xml::dom::NodeList - IXMLDOMNodeList 包装器
    • xml::dom::NamedNodeMap - IXMLDOMNamedNodeMap 包装器
    • xml::dom::DocumentType - IXMLDOMDocumentType 包装器
    • xml::dom::Attribute - IXMLDOMAttribute 包装器
    • xml::dom::Element - IXMLDOMElement 包装器
    • xml::dom::DocumentFragment - IXMLDOMDocumentFragment 包装器
    • xml::dom::CharacterData - IXMLDOMCharacterData 包装器
    • xml::dom::Text - IXMLDOMText 包装器
    • xml::dom::Comment - IXMLDOMComment 包装器
    • xml::dom::CDATASection - IXMLDOMCDATASection 包装器
    • xml::dom::ProcessingInstruction - IXMLDOMProcessingInstruction 包装器
    • xml::dom::EntityReference - IXMLDOMEntityReference 包装器
    • xml::dom::ParseError - IXMLDOMParseError 包装器
    • xml::dom::SchemaCollection - IXMLDOMSchemaCollection 包装器
    • xml::dom::Document - IXMLDOMDocument3 包装器
    • SupportErrorInfo - ISupportErrorInfo 包装器
    • ErrorInfo - IErrorInfo 包装器
    • CreateErrorInfo - ICreateErrorInfo 包装器
    • ProvideClassInfo - IProvideClassInfo 包装器。如果对象支持 IProvideClassInfo2,您将能够调用 GetGUID,但获取 TypeInfo 不需要 IProvideClassInfo2
  • 2012 年 11 月 8 日 - 添加了新类
    • SystemHandle - 可使用 CloseHandle 关闭的句柄的基类
    • WaitableHandle - 同步句柄的基类
    • EventWaitHandle - 线程同步事件
    • AutoResetEvent
    • ManualResetEvent
  • 2012 年 11 月 9 日 - 添加了新类
    • OleAdviseHolder - IOleAdviseHolder 包装器
    • OleCache - IOleCache 包装器
    • OleCache2 - IOleCache2 包装器
    • OleCacheControl - IOleCacheControl 包装器
    • ParseDisplayName_ - IParseDisplayName 包装器
    • OleContainer - IOleContainer 包装器
    • OleClientSite - IOleClientSite 包装器
    • EnumOLEVERB - IEnumOLEVERB 包装器
    • OleObject - IOleObject 包装器
    • OleWindow - IOleWindow 包装器
    • PerlinNoice - 柏林噪声生成器
    • String - 引用计数 string
  • 2012 年 11 月 11 日 - 进行了多项增强并添加了一个新示例 StringsExample,位于 \Examples\Windows\Strings\StringsExample 下。String 是一个引用计数、写时复制的 string 类,其二进制表示形式与零终止 string 相同。String 类的 public 接口如下:
    class String
    {
    public:
        typedef unsigned long long size_type;
        typedef wchar_t value_type;
        static const size_type npos = MAXDWORD64;
        static const wchar_t defaultPadCharacter = L'\x20';
        String();
        String(const String& other);
        String(size_type length, wchar_t c);
        String(const wchar_t* str,size_type length, 
            wchar_t padCharacter = defaultPadCharacter );
        String(const wchar_t* str1,size_type length1, 
            const wchar_t* str2, size_type length2, 
            wchar_t padCharacter = defaultPadCharacter);
        String(const wchar_t* str1,size_type length1, 
            const wchar_t* str2,size_type length2, 
            const wchar_t* str3,size_type length3, 
            wchar_t padCharacter = defaultPadCharacter);
        String(const wchar_t* str);
        String(String&& other);
        ~String();
    
        String& operator = (const String& other);
        String& operator = (const wchar_t* str);
        String& operator = (String&& other);
    
        int CompareTo(const String& other) const;
        int CompareTo(const wchar_t* str) const;
    
        bool operator == (const String& other) const;
        bool operator != (const String& other) const;
        bool operator <= (const String& other) const;
        bool operator <  (const String& other) const;
        bool operator >= (const String& other) const;
        bool operator >  (const String& other) const;
    
        bool operator == (const wchar_t* str) const;
        bool operator != (const wchar_t* str) const;
        bool operator <= (const wchar_t* str) const;
        bool operator <  (const wchar_t* str) const;
        bool operator >= (const wchar_t* str) const;
        bool operator >  (const wchar_t* str) const;
    
        String& Append(const String& other);
        String& Append(const wchar_t* str, size_type length);
        String& Append(const wchar_t* str);
    
        String Appended(const String& other) const;
        String Appended(const wchar_t* str) const;
    
        String& operator += (const String& other);
        String& operator += (const wchar_t* str);
    
        friend String operator + (const String& str1,const String& str2);
        friend String operator + (const String& str1,const wchar_t* str2);
    
        size_type length() const;
        size_type Length() const;
        const wchar_t* c_str() const;
        wchar_t* c_str();
    
        size_type IndexOfAnyOf ( const wchar_t *searchChars, size_type numberOfSearchChars, 
                                size_type start = 0) const;
        size_type IndexOfAnyOf ( const String& searchChars, size_type start = 0) const;
        size_type IndexOfAnyOf( const wchar_t* searchChars, size_type start = 0) const;
    
        size_type IndexOfAnyBut ( const wchar_t *searchChars, size_type numberOfSearchChars, 
                                size_type start = 0) const;
        size_type IndexOfAnyBut ( const String& searchChars, size_type start = 0) const;
        size_type IndexOfAnyBut( const wchar_t* searchChars, size_type start = 0) const;
    
        size_type LastIndexOfAnyOf 
               ( const wchar_t *searchChars, size_type numberOfSearchChars, 
                                size_type start = npos) const;
        size_type LastIndexOfAnyOf( const String& searchChars, size_type start = npos) const;
        size_type LastIndexOfAnyOf( const wchar_t* searchChars, size_type start = npos) const;
    
        size_type LastIndexOfAnyBut 
                  ( const wchar_t *searchChars, size_type numberOfSearchChars, 
                                size_type start = npos) const;
        size_type LastIndexOfAnyBut
               ( const String& searchChars, size_type start = npos) const;
        size_type LastIndexOfAnyBut
               ( const wchar_t* searchChars, size_type start = npos) const;
    
        size_type IndexOf( const wchar_t *searchString, size_type searchStringLength, 
                                size_type start = 0) const;
        size_type IndexOf( const String& searchString, size_type start = 0) const;
        size_type IndexOf( const wchar_t* searchString, size_type start = 0) const;
    
        size_type LastIndexOf( const wchar_t *searchString, size_type searchStringLength, 
                                size_type start = npos) const;
        size_type LastIndexOf( const String& searchString, size_type start = npos) const;
        size_type LastIndexOf( const wchar_t* searchString, size_type start = npos) const;
    
    
        const String& CopyTo( wchar_t* buffer, size_type bufferSize, size_type start = 0, 
                                wchar_t padCharacter = defaultPadCharacter ) const;
    
        String SubString ( size_type start, size_type length = npos) const;
    
        String& UpperCase();
        String& LowerCase();
    
        String& Remove(size_type start, size_type length = npos);
        String& RemoveRange(size_type start, size_type end);
    
        String& Keep(size_type start, size_type length = npos);
        String& KeepRange(size_type start, size_type end);
    
        String& Insert( const wchar_t* text, size_type textLength, size_type position );
        String& Insert( const String& text, size_type position = 0);
        String& Insert( const wchar_t* text, size_type position = 0);
                
        String& TrimRight();
        String& TrimLeft();
        String& Trim();
    };

    我最初使用 std::wstring 类作为库的主要 string 表示形式——现在它已被 String 类取代,因为二进制表示形式可以更容易地与 Windows API 的某些部分集成。

  • 2012 年 11 月 13 日 - 添加了新的 COM 接口实现模板
    • IUnknownImpl
    • ISequentialStreamImpl
    • IStreamImpl
    • IOleControlImpl
    • IOleObjectImpl
    • IOleWindowImpl
    • IOleInPlaceObjectImpl
    • IOleInPlaceActiveObjectImpl
    • IViewObjectImpl
    • IViewObject2Impl
    • IPersistImpl
    • IPersistStreamInitImpl
    • IPersistPropertyBagImpl
    • IPersistStorageImpl
    • IQuickActivateImpl
    • IDropTargetImpl
    • IDropSourceImpl
  • 2012 年 11 月 15 日 - COM/ActiveX 设计变更
    • Object 和派生自 Object 的类不再实现任何 COM 接口,因为那确实没有任何意义。Object 的生命周期由 std::shared_ptr<> 管理,将其与 COM 混合是一个糟糕的主意™。COM 功能现在由代理类实现,这些代理类提供所需的 COM 功能,而不会干扰 Object 的生命周期。
    • UnknownPtr & IUnknownImplementation 可能会很快被移除。
    • COM 接口使用单继承实现——据说这样更容易理解。对象的接口实现共享 IUnknown 实现的公共实例。如果事情按我希望的那样发展,我们将能够在运行时更改接口实现。
    • Object 的 COM 代理类必须派生自 ComObject,而其他 COM 类应该派生自 ComObjectBase,后者管理接口映射。

    添加了新的 COM 类和接口实现模板

    • ComObjectBase - COM 对象的基类
    • ComObject - Object COM 代理类的基类
    • ComStream - 围绕 StreamBaseIStream
    • ComControl - ActiveX 控件实现(处于非常早期阶段)
    • IEnumUnknownImpl - IEnumUnknown 接口实现
    • IEnumVARIANTImpl - IEnumVARIANT 接口实现
    • IEnumStringImpl - IEnumString 接口实现
    • IEnumConnectionPointsImpl - IEnumConnectionPoints 接口实现
    • IConnectionPointContainerImpl - IConnectionPointContainer 接口实现
    • IEnumConnectionsImpl - IEnumConnections 接口实现
    • IConnectionPointImpl - IConnectionPoint 接口实现
    • ComContainerEnumAdapterBase - 适用于 C++ 标准库容器的核心 IEnumXXX 功能
    • template <typename> ComContainerEnumAdapter - 从容器进行通用复制
    • template <typename> ComContainerEnumAdapter<containertype,variant> - 从容器复制,value_type 为 Variant
    • template <typename> ComContainerEnumAdapter<containertype,connectdata> - 从容器复制,value_type 为 ConnectData
    • template <typename> ComContainerEnumAdapter<containertype,iunknown*> - 从容器复制,value_type 为 Unknown
    • template <typename> ComContainerEnumAdapter<containertype,lpconnectionpoint> - 从容器复制,value_type 为 ConnectionPoint
    • template <typename> ComContainerEnumAdapter<containertype,lpolestr>> - 从容器复制,value_type 为 String
    • template <typename> class ComEnum - IEnumXXX 实现模板
    • ComEnumConnections - 结合 ComEnumComContainerEnumAdapter & IEnumConnectionsImpl,在 std::vector<ConnectData> 上实现 IEnumConnections
    • ComEnumConnectionPoints - 结合 ComEnumComContainerEnumAdapter & IEnumConnectionPointsImpl,在 std::vector<ConnectionPoint> 上实现 IEnumConnectionPoints
    • ComEnumUnknown - 结合 ComEnumComContainerEnumAdapter & IEnumUnknownImpl,在 std::vector<Unknown> 上实现 IEnumUnknown
    • ComEnumVARIANT - 结合 ComEnumComContainerEnumAdapter & IEnumVARIANTImpl,在 std::vector<Variant> 上实现 IEnumVARIANT
    • ComEnumString - 结合 ComEnumComContainerEnumAdapter & IEnumStringImpl,在 std::vector<String> 上实现 IEnumString
  • 2012 年 11 月 18 日 - 添加了新类
    • Timer - UI 计时器类
    • Commandline - 命令行实用程序类 - 将 string 分解为命令及其参数,可选择扩展环境变量和通配符
    • Path - 路径管理实用程序类
  • 2012 年 11 月 21 日 - 添加了新示例
    • ButtonExample
    • DropDownButtonExample
    • HeaderControlExample
    每个示例都是“完整”的 Windows C++ 应用程序,其意义在于它们以不到 100 行 C++ 代码说明了程序初始化、事件处理和绘制。

    上述窗体的类声明非常简单

    class HeaderControlForm : public Form
    {
        std::shared_ptr<HeaderControl> headerControl;
    public:
        typedef Form Base;
        HeaderControlForm();
    protected:
        virtual void DoOnInitialize();
        virtual void DoOnSize(Message& message);
        virtual void DoOnPaint(Message& message);
    };

    在构造函数中,我们只需设置窗体的标题

    HeaderControlForm::HeaderControlForm()
        : Base()
    {
        SetText(L"HeaderControl example");
    }

    其余的初始化在 DoOnInitialize() 函数中完成。

    void HeaderControlForm::DoOnInitialize()
    {
        Base::DoOnInitialize(); 
        auto self = As<HeaderControlForm>();
        headerControl = make_control<HeaderControl>(self);
        headerControl->Items()->Add(String(L"First"));
        headerControl->Items()->Add(String(L"Second"));
    }

    在上面的代码中,self 是一个 std::shared_ptr<HeaderControlForm>,这就是为什么我们有一个单独的初始化函数,因为 HeaderControlForm 对象的构造和持有 HeaderControlForm 对象的强引用的初始 std::shared_ptr<HeaderControlForm> 必须在我们可以执行这部分之前完成。

    由于我们没有真正的布局功能,我们需要处理窗体大小的变化

    void HeaderControlForm::DoOnSize(Message& message)
    {
        harlinn::windows::Rectangle clientRect = GetClientRect();
        headerControl->MoveWindow(0,0,clientRect.Width(),21);
    }

    窗体使用桌面壁纸绘制

    void HeaderControlForm::DoOnPaint(Message& message)
    {
        Base::DoOnPaint(message);
        auto dc = std::make_shared<PaintDeviceContextHandle>(As<Control>());
        dc->DrawDesktopWallPaper();
    }

    最后,我们来看 _tWinMain 函数

    int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                           LPTSTR lpCmdLine, int nCmdShow)
    {
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
        try
        {
            auto application = make_component<Application>();
            auto form = make_control<HeaderControlForm>();
            
            auto result = application->Run(form);
    
            return result;
        }
        catch(std::exception& exc)
        {
            std::cout << exc.what() << std::endl;
        }
        catch(...)
        {
            std::cout << "Unknown exception" << std::endl;    
        }
        return 0;
    }

    Windows C++ 开发不会比这更简单了。

  • 2012 年 11 月 22 日 - 添加/更新示例
    • ButtonExample

    • CheckBoxExample

    • CommandButtonExample

    • DropDownButtonExample

    • HeaderControlExample

    • LabelExample

    • LinkLabelExample

    • RadioButtonExample

    • TextEditExample

    • 为准备控件的即时重新创建,与窗口样式相关的代码已重写。
  • 2012 年 11 月 30 日 - 库更新
    • ThreadHandle
    • CurrentThreadHandle
    • StreamCore
    • FileStream
    • 互斥体
    • CriticalSection
    • Lock<T>
    • StringBuilder
  • 2012 年 12 月 15 日 - 库更新
    • 初始并行 SuperLU C++ 实现

    代码基于原始的 SuperLU[^] 实现。

  • 2013 年 2 月 14 日 - 库更新,新类
    • IOleControlSiteImpl
    • IOleAdviseHolderImpl
    • IOleCacheImpl
    • IOleCache2Impl
    • IOleCacheControlImpl
    • IParseDisplayNameImpl
    • IOleContainerImpl
    • IOleClientSiteImpl
    • IOleClientSiteImpl
    • IOleLinkImpl
    • IOleItemContainerImpl
    • IOleInPlaceUIWindowImpl
    • IOleInPlaceFrameImpl
    • IOleInPlaceSiteImpl
    • IDropSourceNotifyImpl
    • IEnumOLEVERBImpl
    • IOleControlSiteImpl
    • IClassFactoryImpl
    • IClassFactory2Impl
    • IProvideClassInfoImpl
    • IProvideClassInfo2Impl
    • IProvideMultipleClassInfoImpl
    • IPropertyPageImpl
    • IPropertyPage2Impl
    • IPropertyPageSiteImpl
    • IPropertyNotifySinkImpl
    • ISpecifyPropertyPagesImpl
    • IPersistMemoryImpl
    • ISimpleFrameSiteImpl
    • IFontImpl
    • IPictureImpl
    • IPicture2Impl
    • IFontEventsDispImpl
    • IFontDispImpl
    • IPictureDispImpl
    • IOleInPlaceObjectWindowlessImpl
    • IOleInPlaceSiteExImpl
    • IOleInPlaceSiteWindowlessImpl
    • IViewObjectExImpl
    • IOleUndoUnitImpl
    • IOleParentUndoUnitImpl
    • IEnumOleUndoUnitsImpl
    • IOleUndoManagerImpl
    • IPointerInactiveImpl
    • IObjectWithSiteImpl
    • IPerPropertyBrowsingImpl
    • IPropertyBag2Impl
    • IPersistPropertyBag2Impl
    • IAdviseSinkImpl
    • IAdviseSinkExImpl
  • 2013 年 5 月 23 日 - 现在构建库更容易,因为项目现在引用两个环境变量
    • BOOST_HOME 设置为包含 boost C++ 库分发目录。
    • HWIN_HOME 设置为包含 HarlinnWindows 解决方案的目录。
  • 2014 年 8 月 20 日 - 多次更新和错误修复
  • 2015 年 1 月 3 日 - 添加了一些新类、一些更新和一些错误修复
© . All rights reserved.