Windows Development in C++, Working with Menus






4.96/5 (59 votes)
Windows API, menus, C++ lambda expressions, std::enable_shared_from_this
Introduction
In this article, we’re going to continue our exploration of Windows C++ development with Visual C++ 2012. We’re going to look at a set of C++ classes that allows us to implement menus programmatically with relative ease.
The purpose of this series is to illustrate an approach to safer and easier C++ development made possible by the C++11 standard, while building our code directly on top of the Windows C and COM based APIs implementing a library that provides a simpler programming model. Even if the accompanying library is a work in progress, it now contains more than 450 usable classes - and it's growing steadily.
Some of the Library Features
- Windows API errors are converted to exceptions
- Maps COM
HRESULT
to exceptions when appropriate - Direct2D wrapper classes
- DirectWrite wrapper classes
- Windows Imaging Component wrapper classes
- Windows Property System wrapper classes
- Core COM utility and interface wrapper classes
- Extensive use of
std::shared_ptr<>
prevents resource leakage - GDI Bitmap, Icon, Font, Brush, Pen and Device Context classes
String
,DateTime
,TimeSpan
and a high resolutionStopwatch
- User interface controls are in the works
- Linear Algebra: Parallel solver for large, sparse, nonsymmetric systems of linear equations
There still remains a lot of work, but feedback, and perhaps an occasional pat on the shoulder is greatly appreciated.
Since the library converts errors to exceptions, we're able to focus on the development task at hand:
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;
}
The series started with Rendering text with Direct2D & DirectWrite[^].
The Windows API provides two functions that allow us to create menus:
HMENU CreateMenu();
HMENU CreatePopupMenu();
The functions return a HMENU
value, which is a handle, and that’s just a numeric identifier, to the menu created by one of the two functions. Handles represents resources allocated by windows on behalf of our applications, so when a handle is no longer needed, it’s important to release those resources back to the operating system. We use the:
BOOL DestroyMenu(HMENU hMenu);
Windows API function to release menu handles that are created programmatically.
What we often think of as the menu bar is one menu, and each drop down, or submenu, is actually separate menus with their own handles, so we’re going to need a decent mechanism that allows us to manage all the handles required by a menu hierarchy appropriately.
Motivation
The code below shows how I would like to use the classes to define a simple menu structure. We should be able to create a menu item using a single statement, and it should be easy to define what we want to happen when the user selects that item from the menu.
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);
}
First we define the five objects that make up the elements of the “File” sub menu. “New”, “Open”, “Save” and “Close” are TextMenuItem
objects, and we also create a SeparatorMenuItem
that specifies the thin line that separates “Close” from the other items.
Then we use C++ lambda expressions to define the actions we want our program to take when the user selects the various menu items, before we create the “File” SubMenuItem
and add the menu items in the order we want them to appear on the menu.
The “Edit” sub menu is created using a slightly different approach, where the “Edit” SubMenuItem
is created first, which allows us to directly add the menu items to the menu.
The horizontal, top-level, menu is represented by a MenuBar
object, and we add out three sub menus to the MenuBar
object before using SetMenu
to add the whole menu structure to our form.
External Libraries
The code relies on the Boost C++ libraries, which can be downloaded from http://www.boost.org[^], so you need to download and build them, before updating the provided projects with include and library paths matching your installation.
Implementation
This started out as an experiment with std::shared_ptr
, the smart pointer template that’s now part of the C++ standard library. If you’re going to recommend a technology, you should be able to build something useful based on your recommendation.
C++ smart pointers aren’t something new, they’ve been around in various incarnations for nearly as long as C++; they’re a highly useful construct.
So as a practical example on the usefulness of std::shared_ptr
, I’m building a C++ library for user interface development, and I’m now starting to get a real feel for where I’m heading:
This seems like a workable design, somewhat based on .NET, while considering elements from what I think of as its predecessor: Embarcaderos’ VCL; and even IBMs’ User Interface Class Library.
The library is in its very early design phase, but I still think it can be of some interest – particularly to those who wonder about the usefulness of std::shared_ptr
.
The notion of handles is pretty prevalent, so it makes sense to have a Handle
class that will serve as the base class for the various types of handles. The Handle
class has a virtual destructor, so we’re able to implement types that will use the correct Windows API function to destroy the resource, even if the destructor is called on a pointer or reference to a Handle
object.
While we could have implemented the functionality required for menu items in a single class, I think it makes sense to have a MenuItem
base class, and then implement specific types representing the various intended uses for the menu items. I think this makes the code using the classes more readable.
In the Windows API, a window receives notifications through a user defined call back procedure registered with the operating system for a particular window class, and menu event notifications works through this mechanism. The library provides a mechanism that routes these event notifications to the...
HWIN_EXPORT virtual void HandleMessage(Message& message);
...function declared in the Control
class. The function takes a reference to a Message
object, representing the notification sent from Windows to the window, as its argument and routes messages to their respective handler functions, for instance will a WM_MENUCOMMAND
message be routed to the...
HWIN_EXPORT virtual void DoOnMenuCommand(Message& message);
...function. The Message
class is derived from the Windows tagMSG
structure...
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
...and provides two additional fields...
class Message : public tagMSG
{
public:
typedef LRESULT Result;
Result result;
bool handled;
Message()
: result(0),
handled(false)
{
memset(&hwnd,0,sizeof(tagMSG));
}
};
... result lets the handler function set the value that will be returned to Windows from the callback as long as handled
is set to true
. By default, handled
is set to false
, causing the message to be passed on to the DefWindowProc
function, which performs a default action and returns a message result.
The Form
class, which represents top-level windows, is derived from the Control
class – and adds functionality specific to top-level windows.
The class uses a std::shared_ptr
<menubar> menuBar
member maintaining a reference to the current menu bar, which can be set using the SetMenu
function, and retrieved using the GetMenu
function.
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);
.
.
};
Now that we have established how notifications pass from windows to our application, it’s time to take a look at how we can use the Windows API to implement menus and how to route WM_MENUCOMMAND
events received by the application to the OnClick boost::signals2::signal
<void> for our MenuItem
implementation.
The first thing we’ll look at is how to implement the constructor for the menu bar:
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);
}
The static CreateHandle
function for the MenuBar
class is called by the constructor and passes a valid handle to a menu to the base class; or throws a std::exception
containing information retrieved from Windows about the error that prevented Windows from creating that handle.
Then we call AddStyle
to add MNS_NOTIFYBYPOS
to the style of the newly created menu.
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;
}
The MNS_NOTIFYBYPOS
style causes Windows to send WM_MENUCOMMAND
events instead of WM_COMMAND
events when the user makes selections. The WM_MENUCOMMAND
provides information that identifies the menu by its handle and the offset of the selected item into that menu, which is more appropriate for our implementation, as we don’t need to keep track of command ids and map them to specific menu items.
Each menu has a list of items, managed by a MenuItems
object that the Menu
class creates in its constructor:
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
provides a mechanism that allows an object obj
that is currently managed by a std::shared_ptr ptr0
to safely generate additional std::shared_ptr
instances ptr1
, ptr2
– all sharing ownership of obj
with ptr0
.
Inheriting from std::enable_shared_from_this<T>
provides the type T
with a member function shared_from_this
. When an object obj
of type T
is managed by a std::shared_ptr<T>
named ptr
, calling T::shared_from_this
will return a new std::shared_ptr<T>
that shares ownership of obj
with ptr
.
Before calling shared_from_this
on obj
, there must be an existing std::shared_ptr
that owns obj
. So you cannot call shared_from_this
inside the constructor to get a std::shared_ptr
to the object under construction.
Note that you should use shared_from_this
in place of expressions like std::shared_ptr<T>(this)
– as the latter is likely to result in this
being destroyed more than once by multiple owners that are unaware of each other.
In the case of classes derived from Object
shared_from_this
returns a std::shared_ptr<Object>
object that we often want to turn into a std::shared_ptr<T>
where T
is a type derived from Object
.
The Object
class provides a few helper template functions that eases downcasting, that should be somewhat familiar to .NET developers:
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())
will return a new instance of std::shared_ptr<T>
with a casted managed object type from the std::shared_ptr<Object>
returned by shared_from_this()
. Both smart pointers will share the ownership of the managed object.
MenuItems
The MenuItems
class is a simple container for MenuItem
objects, that holds each MenuItem
object using a std::shared_ptr<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);
};
Since the MenuItems
object is created by the Menu
constructor, we cannot pass a std::shared_ptr<Menu>
object to the MenuItems
constructor, we need to pass a raw pointer to the Menu
object under construction.
The Menu()
function returns a std::shared_ptr< Menu >
which can easily be retrieved from owner
using the As<T>()
template function.
HWIN_EXPORT std::shared_ptr<Menu> MenuItems::Menu() const
{
if(owner)
{
return owner->As<harlinn::windows::Menu>();
}
return std::shared_ptr<harlinn::windows::Menu>();
}
The Add
function sets the parentMenu
, which is a std::weak_ptr<Menu>
object that now provides the MenuItem
object access, through the ParentMenu
function, to the Menu
it belongs to.
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;
}
Next, we add the std::shared_ptr<MenuItem>
to the items vector, which ensures that the MenuItem
objects will exist for the lifetime of this MenuItems
object, or until they are removed. Now that the housekeeping is in order, we call the DoOnAdd()
function of the MenuItem
class.
Inside the ParentMenu()
function, we use the lock()
function to turn the std::weak_ptr<Menu>
into a std::shared_ptr<Menu>
object.
Both the lock()
function and the constructor of std::shared_ptr
can be used to acquire ownership of a managed object from a std::weak_ptr
. The difference is that the constructor of std::shared_ptr<T>
throws an exception when the std::weak_ptr
argument is empty, while std::weak_ptr<T>::lock()
constructs an empty std::shared_ptr<T>
.
HWIN_EXPORT std::shared_ptr<Menu> MenuItem::ParentMenu() const
{
auto theParentMenu = parentMenu.lock();
return theParentMenu;
}
MenuItem
Each item on a menu is represented by a MenuItem
object:
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);
The DoOnAdd()
function is responsible for adding the Windows menu item for the MenuItem
object to the menu it belongs to.
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;
}
The InitializeMenuItemInfo
function is responsible for initializing the MENUITEMINFO
structure, in particular it sets the dwItemData
member of the structure to the this
pointer. The Windows WM_DRAWITEM
and WM_MEASUREITEM
notifications pass this value back to the application, allowing us to determine which MenuItem
object we’re going to draw or measure.
WM_MENUCOMMAND, WM_DRAWITEM & WM_MEASUREITEM
The HandleMessage
function for the Control
class is basically a switch
statement that routes notifications to their respective handler functions:
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 sends WM_MEASUREITEM
and WM_DRAWITEM
notifications to the window callback function when it needs to get measurement information, or provide the application with the ability to perform its own rendering of owner drawn elements. Since InitializeMenuItemInfo
provided Windows with a pointer to the MenuItem
, we can now access that pointer using the itemData
member of the MEASUREITEMSTRUCT
or DRAWITEMSTRUCT
and call the DoOnMeasureItem
or DoOnDrawItem
on the MenuItem
object as required.
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;
}
}
}
}
We use CtlType
to determine that the notification is for a menu, set handled to true
to prevent the notification from being passed to the DefWindowProc
and set result to true
, which lets Windows know that we have handled the message.
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;
}
}
}
}
The WM_MENUCOMMAND
, the notification Windows sends when the user selects an item on the menu, does not provide the pointer to the MenuItem
object. What we get is the handle to the menu and the position of the selected menu item on that menu.
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);
}
}
}
}
The Menu
class maintains an internal map of menu handles to Menu
objects that provides easy access to a Menu
object, given its handle, using the static GetFromMenuMap
function of the Menu
class, allowing the Control
class to pass the notification to the Menu
object.
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);
}
}
}
The Menu
class uses the position information provided by Windows to get the MenuItem
object corresponding to that position before calling the DoOnMenuCommand
on that object.
HWIN_EXPORT void MenuItem::DoOnMenuCommand(Message& message)
{
OnClick( );
}
DoOnMenuCommand
just raises the OnClick
signal on the MenuItem
, executing those C++ lambda expressions we assigned when we created the menu in the MyForm::InitializeMenuBar()
function that we went through earlier.
History
- 6th October, 2012 - Initial posting
- 20th October, 2012 - Bug fixes and a wide range of changes to the library
- 21st October, 2012 - Added a few new classes:
Environment::KnownFolder
- wraps theSHGetKnownFolderPath
functionEnvironment::UserName
- wraps theGetUserNameEx
functionEnvironment::ComputerName
- wraps theGetComputerNameEx
functionEnvironment::SystemMetrics
- wraps theGetSystemMetrics
function
- 23rd October, 2012 - Added a few new classes:
ColorRef
BrushHandle
PenHandle
FontHandle
RegionHandle
PaletteHandle
DeviceContextHandle
: Text, lines, shapes, bitmaps, transformationsPaintDeviceContextHandle
MemoryDeviceContextHandle
BitmapSelection
: ensures that a bitmap selected into a device context gets unselectedBrushSelection
: ensures that a brush selected into a device context gets unselectedPenSelection
: ensures that a pen selected into a device context gets unselectedFontSelection
: ensures that a font selected into a device context gets unselected
- 25th October, 2012 - Added a few new features:
BitmapHandle::LoadFromFile
- loads PNG, JPG, BMP, GIF images
DeviceContextExample
, demonstratingDeviceContextHandle
:DrawCaption
DrawCaptionFrameControl
DrawMenuBarFrameControl
DrawScrollBarFrameControl
DrawButtonFrameControl
DrawEdge
DrawFocusRect
DrawState
DrawDesktopWallPaper
- 30th October, 2012 - Added new classes:
Variant
- Flexible COMVARIANT
wrapper with some nice features:- Full set of comparison operators
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
wrapperSysString
-COM BSTR
wrapperSafeArray
-COM SAFEARRAY
wrapperMarshal
-IMarshal
wrapperMalloc
-IMalloc
wrapperEnumUnknown
-IEnumUnknown
wrapperSequentialStream
-ISequentialStream
wrapperStream
-IStream
wrapperPersist
-IPersist
wrapperPersistStream
-IPersistStream
EnumMoniker
-IEnumMoniker
wrapperMoniker
-IMoniker
wrapperRunningObjectTable
-IRunningObjectTable
wrapperBindCtx
-IBindCtx
wrapperRunnableObject
-IRunnableObject
wrapperROTData
-IROTData
wrapperEnumSTATSTG
-IEnumSTATSTG
wrapperStorage
-IStorage
wrapperPersistFile
-IPersistFile
wrapperPersistStorage
-IPersistStorage
wrapperLockBytes
-ILockBytes
wrapperEnumFORMATETC
-IEnumFORMATETC
wrapperEnumSTATDATA
-IEnumSTATDATA
wrapperRootStorage
-IRootStorage
wrapperAdviseSink
-IAdviseSink
wrapperAsyncAdviseSink
-AsyncIAdviseSink
wrapperAdviseSink2
-IAdviseSink2
wrapperAsyncAdviseSink2
-AsyncIAdviseSink2
wrapperStgMedium
- manageSTGMEDIUM
lifetimeDataObject
-IDataObject
wrapperDataAdviseHolder
-IDataAdviseHolder
MessageFilter
-IMessageFilter
wrapperClassActivator
-IClassActivator
wrapperFillLockBytes
-IFillLockBytes
wrapperProgressNotify
-IProgressNotify
wrapperLayoutStorage
-ILayoutStorage
wrapperBlockingLock
-IBlockingLock
wrapperDirectWriterLock
-IDirectWriterLock
wrapperForegroundTransfer
-IForegroundTransfer
wrapperProcessLock
-IProcessLock
wrapperSurrogateService
-ISurrogateService
wrapperInitializeWithFile
-IInitializeWithFile
wrapperInitializeWithStream
-IInitializeWithStream
wrapperPropertyStore
-IPropertyStore
wrapperNamedPropertyStore
-INamedPropertyStore
wrapperObjectWithPropertyKey
-IObjectWithPropertyKey
wrapperPropertyChange
-IPropertyChange
wrapperPropertyChangeArray
-IPropertyChangeArray
wrapperPropertyStoreCapabilities
-IPropertyStoreCapabilities
wrapperPropertyStoreCache
-IPropertyStoreCache
wrapperPropertyEnumType
-IPropertyEnumType
wrapperPropertyEnumType2
-IPropertyEnumType2
wrapperPropertyEnumTypeList
-IPropertyEnumTypeList
wrapperPropertyDescription
-IPropertyDescription
wrapperPropertyDescriptionList
-IPropertyDescriptionList
wrapperPropertyDescription2
-IPropertyDescription2
wrapperPropertyDescriptionAliasInfo
-IPropertyDescriptionAliasInfo
wrapperPropertyDescriptionSearchInfo
-IPropertyDescriptionSearchInfo
wrapperPropertyDescriptionRelatedPropertyInfo
-IPropertyDescriptionRelatedPropertyInfo
wrapperPropertySystem
-IPropertySystem
wrapperPropertyStoreFactory
-IPropertyStoreFactory
wrapperDelayedPropertyStoreFactory
-IDelayedPropertyStoreFactory
wrapperPersistSerializedPropStorage
-IPersistSerializedPropStorage
wrapperPersistSerializedPropStorage2
-IPersistSerializedPropStorage2
wrapperPropertySystemChangeNotify
-IPropertySystemChangeNotify
wrapperCreateObject_
-ICreateObject
wrapper
Unknown
class:template<typename T> T As() const { ... }
which provides a nice alternative toQueryInterface
, assumingunk
is anUnknown
object:Stream stream = unk.As<Stream>();
which will work as expected as long as the wrapped object actually supports the
IStream
interface, otherwise thestream
object will be empty.if(stream) { stream.Seek(100,SeekOrigin::StartOfFile); . . . }
- 4th November, 2012 - Added new classes:
TypeComp
-ITypeComp
wrapperTypeInfo
-ITypeInfo
wrapperTypeLib
-ITypeLib
wrapperDispatch
-IDispatch
wrapperShellItem
-IShellItem2
wrapperEnumShellItems
-IEnumShellItems
wrapperShellItemArray
-IShellItemArray
wrapperModalWindow
-IModalWindow
wrapperFileDialogEvents
-IFileDialogEvents
wrapperFileDialogEventsImplementation
-IFileDialogEvents
implementationFileDialog
-IFileDialog
wrapperFileOpenDialog
-IFileOpenDialog
wrapperFileSaveDialog
-IFileSaveDialog
wrapperxml::dom::Implementation
-IXMLDOMImplementation
wrapperxml::dom::Node
-IXMLDOMNode
wrapperxml::dom::NodeList
-IXMLDOMNodeList
wrapperxml::dom::NamedNodeMap
-IXMLDOMNamedNodeMap
wrapperxml::dom::DocumentType
-IXMLDOMDocumentType
wrapperxml::dom::Attribute
-IXMLDOMAttribute
wrapperxml::dom::Element
-IXMLDOMElement
wrapperxml::dom::DocumentFragment
-IXMLDOMDocumentFragment
wrapperxml::dom::CharacterData
-IXMLDOMCharacterData
wrapperxml::dom::Text
-IXMLDOMText
wrapperxml::dom::Comment
-IXMLDOMComment
wrapperxml::dom::CDATASection
-IXMLDOMCDATASection
wrapperxml::dom::ProcessingInstruction
-IXMLDOMProcessingInstruction
wrapperxml::dom::EntityReference
-IXMLDOMEntityReference
wrapperxml::dom::ParseError
-IXMLDOMParseError
wrapperxml::dom::SchemaCollection
-IXMLDOMSchemaCollection
wrapperxml::dom::Document
-IXMLDOMDocument3
wrapperSupportErrorInfo
-ISupportErrorInfo
wrapperErrorInfo
-IErrorInfo
wrapperCreateErrorInfo
-ICreateErrorInfo
wrapperProvideClassInfo
-IProvideClassInfo
wrapper. If the object supportsIProvideClassInfo2
, you'll be able to callGetGUID
, butIProvideClassInfo2
is not required to get at the TypeInfo.
- 8th November, 2012 - Added new classes:
SystemHandle
- Base class for handles that can be closed usingCloseHandle
WaitableHandle
- Base class for synchronization handlesEventWaitHandle
- thread synchronization eventAutoResetEvent
ManualResetEvent
- 9th November, 2012 - Added new classes:
OleAdviseHolder
-IOleAdviseHolder
wrapperOleCache
-IOleCache
wrapperOleCache2
-IOleCache2
wrapperOleCacheControl
-IOleCacheControl
wrapperParseDisplayName_
-IParseDisplayName
wrapperOleContainer
-IOleContainer
wrapperOleClientSite
-IOleClientSite
wrapperEnumOLEVERB
-IEnumOLEVERB
wrapperOleObject
-IOleObject
wrapperOleWindow
-IOleWindow
wrapperPerlinNoice
- A perlin noice generatorString
- Reference countedstring
class
- 11th November, 2012 - A number of enhancements and a new example,
StringsExample
, located under \Examples\Windows\Strings\StringsExample.String
is a reference counted, copy on write,string
class with a binary representation equal to that of a zero terminatedstring
. Thepublic
interface to theString
class looks like this: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(); };
I initially used the
std::wstring
class as the primarystring
representation for the library - it has now been replaced by theString
class because the binary representation provides for easier integration with some parts of the Windows API. - 13th November, 2012 - Added new COM interface implementation templates:
IUnknownImpl
ISequentialStreamImpl
IStreamImpl
IOleControlImpl
IOleObjectImpl
IOleWindowImpl
IOleInPlaceObjectImpl
IOleInPlaceActiveObjectImpl
IViewObjectImpl
IViewObject2Impl
IPersistImpl
IPersistStreamInitImpl
IPersistPropertyBagImpl
IPersistStorageImpl
IQuickActivateImpl
IDropTargetImpl
IDropSourceImpl
- 15th November, 2012 - COM/ActiveX design changes:
- Object and classes derived from
Object
no longer implment any COM interfaces because that really didn't make any sense. The lifetime of anObject
is managed by astd::shared_ptr<>
and mixing that with COM was a Bad Idea™. COM functionality is now implemented by proxy classes that provides the required COM functionality without messing with the lifetime of anObject
. UnknownPtr
&IUnknownImplementation
will probably be removed pretty soon.- COM interfaces are implemented using single inheritance - which, according to rumours, is supposed to be easier to understand. The interface implementations for an object shares a common instance of the
IUnknown
implementation. If things work out as I hope they will, we will be able to change interface implementations at runtime. - COM proxy classes for
Object
must be derived fromComObject
, while other COM classes should be derived fromComObjectBase
which manages the interface map.
Added new COM classes & interface implementation templates:
ComObjectBase
- base class for COM objectsComObject
- base class for Object COM proxy classesComStream
-IStream
around aStreamBase
ComControl
- ActiveX control implementation (at a very early stage)IEnumUnknownImpl
-IEnumUnknown
interface implementationIEnumVARIANTImpl
-IEnumVARIANT
interface implementationIEnumStringImpl
-IEnumString
interface implementationIEnumConnectionPointsImpl
-IEnumConnectionPoints
interface implementationIConnectionPointContainerImpl
-IConnectionPointContainer
interface implementationIEnumConnectionsImpl
-IEnumConnections
interface implementationIConnectionPointImpl
-IConnectionPoint
interface implementationComContainerEnumAdapterBase
- CoreIEnumXXX
functionality suitable for use with the containers from the C++ standard librarytemplate <typename> ComContainerEnumAdapter
- Generic copy from containertemplate <typename> ComContainerEnumAdapter<containertype,variant>
- Copy from container with value_type of Varianttemplate <typename> ComContainerEnumAdapter<containertype,connectdata>
- Copy from container with value_type of ConnectDatatemplate <typename> ComContainerEnumAdapter<containertype,iunknown*>
- Copy from container with value_type of Unknowntemplate <typename> ComContainerEnumAdapter<containertype,lpconnectionpoint>
- Copy from container with value_type of ConnectionPointtemplate <typename> ComContainerEnumAdapter<containertype,lpolestr>>
- Copy from container with value_type of Stringtemplate <typename> class ComEnum
-IEnumXXX
implementation templateComEnumConnections
- CombinesComEnum
,ComContainerEnumAdapter
&IEnumConnectionsImpl
, implementingIEnumConnections
on astd::vector<ConnectData>
ComEnumConnectionPoints
- CombinesComEnum
,ComContainerEnumAdapter
&IEnumConnectionPointsImpl
, implementingIEnumConnectionPoints
on astd::vector<ConnectionPoint>
ComEnumUnknown
- CombinesComEnum
,ComContainerEnumAdapter
&IEnumUnknownImpl
, implementingIEnumUnknown
on astd::vector<Unknown>
ComEnumVARIANT
- CombinesComEnum
,ComContainerEnumAdapter
&IEnumVARIANTImpl
, implementingIEnumVARIANT
on astd::vector<Variant>
ComEnumString
- CombinesComEnum
,ComContainerEnumAdapter
&IEnumStringImpl
, implementingIEnumString
on astd::vector<String>
- Object and classes derived from
- 18th November, 2012 - Added new classes:
Timer
- a UI timer classCommandline
- acommandline
utility class - breaks up astring
into a command and its arguments, optionally expanding environment variables and wildcardsPath
- a path management utility class
- 21. of November, 2012 - Added new examples:
ButtonExample
DropDownButtonExample
HeaderControlExample
The class declaration for the above form is pretty simple:
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); };
In the constructor, we just set title for the form:
HeaderControlForm::HeaderControlForm() : Base() { SetText(L"HeaderControl example"); }
While the rest of the initialization is done in the
DoOnInitialize()
function.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")); }
In the above code,
self
is astd::shared_ptr<HeaderControlForm>
which is why we have a separate initialization function, as the construction of theHeaderControlForm
object and the initialstd::shared_ptr<HeaderControlForm>
holding a strong reference theHeaderControlForm
object has to be completed before we can perform this part.Since we don't have real layout functionality, we need to handle changes to the size of the form:
void HeaderControlForm::DoOnSize(Message& message) { harlinn::windows::Rectangle clientRect = GetClientRect(); headerControl->MoveWindow(0,0,clientRect.Width(),21); }
The form is painted using the desktop wallpaper:
void HeaderControlForm::DoOnPaint(Message& message) { Base::DoOnPaint(message); auto dc = std::make_shared<PaintDeviceContextHandle>(As<Control>()); dc->DrawDesktopWallPaper(); }
And finally, we get to our
_tWinMain
function: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++ development doesn't get much simpler than this.
- 22nd November, 2012 - Added/updated examples:
ButtonExample
CheckBoxExample
CommandButtonExample
DropDownButtonExample
HeaderControlExample
LabelExample
LinkLabelExample
RadioButtonExample
TextEditExample
- Code related to window style has been rewritten in preparation for on-the-fly recreation of controls.
- 30th November, 2012 - Library update:
ThreadHandle
CurrentThreadHandle
StreamCore
FileStream
Mutex
CriticalSection
Lock<T>
StringBuilder
- 15th December, 2012 - Library update:
- Initial parallel SuperLU C++ implementation
The code is based on the original SuperLU[^] implementation.
- 14th February, 2013 - Library update, new classes:
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
- 23rd May, 2013 - It's now easier to build the library since the project now references two environment variables:
- Set
BOOST_HOME
to the directory containing the boost C++ library distribution. - Set
HWIN_HOME
to the directory containing theHarlinnWindows
solution.
- Set
- 20th August, 2014 - More than a few updates and bug-fixes
- 3rd January, 2015 - A few new classes, some updates and a number of bug-fixes