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






4.96/5 (59投票s)
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 位图、图标、字体、画刷、画笔和设备上下文类
String
、DateTime
、TimeSpan
和高分辨率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
。
然后我们调用 AddStyle
将 MNS_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
实例 ptr1
、ptr2
– 所有这些都与 ptr0
共享 obj
的所有权。
从 std::enable_shared_from_this<T>
继承为类型 T
提供了一个成员函数 shared_from_this
。当类型 T
的对象 obj
由名为 ptr
的 std::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_DRAWITEM
和 WM_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_MEASUREITEM
和 WM_DRAWITEM
通知。由于 InitializeMenuItemInfo
为 Windows 提供了指向 MenuItem
的指针,我们现在可以使用 MEASUREITEMSTRUCT
或 DRAWITEMSTRUCT
的 itemData
成员访问该指针,并根据需要对 MenuItem
对象调用 DoOnMeasureItem
或 DoOnDrawItem
。
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
- 灵活的 COMVARIANT
包装器,带有一些不错的功能- 完整的比较运算符集
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
- 围绕StreamBase
的IStream
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 为 Varianttemplate <typename> ComContainerEnumAdapter<containertype,connectdata>
- 从容器复制,value_type 为 ConnectDatatemplate <typename> ComContainerEnumAdapter<containertype,iunknown*>
- 从容器复制,value_type 为 Unknowntemplate <typename> ComContainerEnumAdapter<containertype,lpconnectionpoint>
- 从容器复制,value_type 为 ConnectionPointtemplate <typename> ComContainerEnumAdapter<containertype,lpolestr>>
- 从容器复制,value_type 为 Stringtemplate <typename> class ComEnum
-IEnumXXX
实现模板ComEnumConnections
- 结合ComEnum
、ComContainerEnumAdapter
&IEnumConnectionsImpl
,在std::vector<ConnectData>
上实现IEnumConnections
ComEnumConnectionPoints
- 结合ComEnum
、ComContainerEnumAdapter
&IEnumConnectionPointsImpl
,在std::vector<ConnectionPoint>
上实现IEnumConnectionPoints
ComEnumUnknown
- 结合ComEnum
、ComContainerEnumAdapter
&IEnumUnknownImpl
,在std::vector<Unknown>
上实现IEnumUnknown
ComEnumVARIANT
- 结合ComEnum
、ComContainerEnumAdapter
&IEnumVARIANTImpl
,在std::vector<Variant>
上实现IEnumVARIANT
ComEnumString
- 结合ComEnum
、ComContainerEnumAdapter
&IEnumStringImpl
,在std::vector<String>
上实现IEnumString
- Object 和派生自
- 2012 年 11 月 18 日 - 添加了新类
Timer
- UI 计时器类Commandline
- 命令行实用程序类 - 将string
分解为命令及其参数,可选择扩展环境变量和通配符Path
- 路径管理实用程序类
- 2012 年 11 月 21 日 - 添加了新示例
ButtonExample
DropDownButtonExample
HeaderControlExample
上述窗体的类声明非常简单
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++ 实现
- 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 日 - 添加了一些新类、一些更新和一些错误修复