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 resolution Stopwatch
- 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 the SHGetKnownFolderPath
function Environment::UserName
- wraps the GetUserNameEx
function Environment::ComputerName
- wraps the GetComputerNameEx
function Environment::SystemMetrics
- wraps the GetSystemMetrics
function
The classes make it easier to retrieve the information from the system. - 23rd October, 2012 - Added a few new classes:
ColorRef
BrushHandle
PenHandle
FontHandle
RegionHandle
PaletteHandle
DeviceContextHandle
: Text, lines, shapes, bitmaps, transformations PaintDeviceContextHandle
MemoryDeviceContextHandle
BitmapSelection
: ensures that a bitmap selected into a device context gets unselected BrushSelection
: ensures that a brush selected into a device context gets unselected PenSelection
: ensures that a pen selected into a device context gets unselected FontSelection
: 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
Added new example, DeviceContextExample
, demonstrating DeviceContextHandle
:
DrawCaption
DrawCaptionFrameControl
DrawMenuBarFrameControl
DrawScrollBarFrameControl
DrawButtonFrameControl
DrawEdge
DrawFocusRect
DrawState
DrawDesktopWallPaper
- 30th October, 2012 - Added new classes:
Variant
- Flexible COM VARIANT
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
wrapper SysString
- COM BSTR
wrapper SafeArray
- COM SAFEARRAY
wrapper Marshal
- IMarshal
wrapper Malloc
- IMalloc
wrapper EnumUnknown
- IEnumUnknown
wrapper SequentialStream
- ISequentialStream
wrapper Stream
- IStream
wrapper Persist
- IPersist
wrapper PersistStream
- IPersistStream
EnumMoniker
- IEnumMoniker
wrapper Moniker
- IMoniker
wrapper RunningObjectTable
- IRunningObjectTable
wrapper BindCtx
- IBindCtx
wrapper RunnableObject
- IRunnableObject
wrapper ROTData
- IROTData
wrapper EnumSTATSTG
- IEnumSTATSTG
wrapper Storage
- IStorage
wrapper PersistFile
- IPersistFile
wrapper PersistStorage
- IPersistStorage
wrapper LockBytes
- ILockBytes
wrapper EnumFORMATETC
- IEnumFORMATETC
wrapper EnumSTATDATA
- IEnumSTATDATA
wrapper RootStorage
- IRootStorage
wrapper AdviseSink
- IAdviseSink
wrapper AsyncAdviseSink
- AsyncIAdviseSink
wrapper AdviseSink2
- IAdviseSink2
wrapper AsyncAdviseSink2
- AsyncIAdviseSink2
wrapper StgMedium
- manage STGMEDIUM
lifetime DataObject
- IDataObject
wrapper DataAdviseHolder
- IDataAdviseHolder
MessageFilter
- IMessageFilter
wrapper ClassActivator
- IClassActivator
wrapper FillLockBytes
- IFillLockBytes
wrapper ProgressNotify
- IProgressNotify
wrapper LayoutStorage
- ILayoutStorage
wrapper BlockingLock
- IBlockingLock
wrapper DirectWriterLock
- IDirectWriterLock
wrapper ForegroundTransfer
- IForegroundTransfer
wrapper ProcessLock
- IProcessLock
wrapper SurrogateService
- ISurrogateService
wrapper InitializeWithFile
- IInitializeWithFile
wrapper InitializeWithStream
- IInitializeWithStream
wrapper PropertyStore
- IPropertyStore
wrapper NamedPropertyStore
- INamedPropertyStore
wrapper ObjectWithPropertyKey
- IObjectWithPropertyKey
wrapper PropertyChange
- IPropertyChange
wrapper PropertyChangeArray
- IPropertyChangeArray
wrapper PropertyStoreCapabilities
- IPropertyStoreCapabilities
wrapper PropertyStoreCache
- IPropertyStoreCache
wrapper PropertyEnumType
- IPropertyEnumType
wrapper PropertyEnumType2
- IPropertyEnumType2
wrapper PropertyEnumTypeList
- IPropertyEnumTypeList
wrapper PropertyDescription
- IPropertyDescription
wrapper PropertyDescriptionList
- IPropertyDescriptionList
wrapper PropertyDescription2
- IPropertyDescription2
wrapper PropertyDescriptionAliasInfo
- IPropertyDescriptionAliasInfo
wrapper PropertyDescriptionSearchInfo
- IPropertyDescriptionSearchInfo
wrapper PropertyDescriptionRelatedPropertyInfo
- IPropertyDescriptionRelatedPropertyInfo
wrapper PropertySystem
- IPropertySystem
wrapper PropertyStoreFactory
- IPropertyStoreFactory
wrapper DelayedPropertyStoreFactory
- IDelayedPropertyStoreFactory
wrapper PersistSerializedPropStorage
- IPersistSerializedPropStorage
wrapper PersistSerializedPropStorage2
- IPersistSerializedPropStorage2
wrapper PropertySystemChangeNotify
- IPropertySystemChangeNotify
wrapper CreateObject_
- ICreateObject
wrapper
Added a new function to the Unknown
class:
template<typename T>
T As() const
{
...
}
which provides a nice alternative to QueryInterface
, assuming unk
is an Unknown
object:
Stream stream = unk.As<Stream>();
which will work as expected as long as the wrapped object actually supports the IStream
interface, otherwise the stream
object will be empty.
if(stream)
{
stream.Seek(100,SeekOrigin::StartOfFile);
.
.
.
}
- 4th November, 2012 - Added new classes:
TypeComp
- ITypeComp
wrapper TypeInfo
- ITypeInfo
wrapper TypeLib
- ITypeLib
wrapper Dispatch
- IDispatch
wrapper ShellItem
- IShellItem2
wrapper EnumShellItems
- IEnumShellItems
wrapper ShellItemArray
- IShellItemArray
wrapper ModalWindow
- IModalWindow
wrapper FileDialogEvents
- IFileDialogEvents
wrapper FileDialogEventsImplementation
- IFileDialogEvents
implementation FileDialog
- IFileDialog
wrapper FileOpenDialog
- IFileOpenDialog
wrapper FileSaveDialog
- IFileSaveDialog
wrapper xml::dom::Implementation
- IXMLDOMImplementation
wrapper xml::dom::Node
- IXMLDOMNode
wrapper xml::dom::NodeList
- IXMLDOMNodeList
wrapper xml::dom::NamedNodeMap
- IXMLDOMNamedNodeMap
wrapper xml::dom::DocumentType
- IXMLDOMDocumentType
wrapper xml::dom::Attribute
- IXMLDOMAttribute
wrapper xml::dom::Element
- IXMLDOMElement
wrapper xml::dom::DocumentFragment
- IXMLDOMDocumentFragment
wrapper xml::dom::CharacterData
- IXMLDOMCharacterData
wrapper xml::dom::Text
- IXMLDOMText
wrapper xml::dom::Comment
- IXMLDOMComment
wrapper xml::dom::CDATASection
- IXMLDOMCDATASection
wrapper xml::dom::ProcessingInstruction
- IXMLDOMProcessingInstruction
wrapper xml::dom::EntityReference
- IXMLDOMEntityReference
wrapper xml::dom::ParseError
- IXMLDOMParseError
wrapper xml::dom::SchemaCollection
- IXMLDOMSchemaCollection
wrapper xml::dom::Document
- IXMLDOMDocument3
wrapper SupportErrorInfo
- ISupportErrorInfo
wrapper ErrorInfo
- IErrorInfo
wrapper CreateErrorInfo
- ICreateErrorInfo
wrapper ProvideClassInfo
- IProvideClassInfo
wrapper. If the object supports IProvideClassInfo2
, you'll be able to call GetGUID
, but IProvideClassInfo2
is not required to get at the TypeInfo.
- 8th November, 2012 - Added new classes:
SystemHandle
- Base class for handles that can be closed using CloseHandle
WaitableHandle
- Base class for synchronization handles EventWaitHandle
- thread synchronization event AutoResetEvent
ManualResetEvent
- 9th November, 2012 - Added new classes:
OleAdviseHolder
- IOleAdviseHolder
wrapper OleCache
- IOleCache
wrapper OleCache2
- IOleCache2
wrapper OleCacheControl
- IOleCacheControl
wrapper ParseDisplayName_
- IParseDisplayName
wrapper OleContainer
- IOleContainer
wrapper OleClientSite
- IOleClientSite
wrapper EnumOLEVERB
- IEnumOLEVERB
wrapper OleObject
- IOleObject
wrapper OleWindow
- IOleWindow
wrapper PerlinNoice
- A perlin noice generator String
- Reference counted string
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 terminated string
. The public
interface to the String
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 primary string
representation for the library - it has now been replaced by the String
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 an Object
is managed by a std::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 an Object
. 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 from ComObject
, while other COM classes should be derived from ComObjectBase
which manages the interface map.
Added new COM classes & interface implementation templates:
ComObjectBase
- base class for COM objects ComObject
- base class for Object COM proxy classes ComStream
- IStream
around a StreamBase
ComControl
- ActiveX control implementation (at a very early stage) IEnumUnknownImpl
- IEnumUnknown
interface implementation IEnumVARIANTImpl
- IEnumVARIANT
interface implementation IEnumStringImpl
- IEnumString
interface implementation IEnumConnectionPointsImpl
- IEnumConnectionPoints
interface implementation IConnectionPointContainerImpl
- IConnectionPointContainer
interface implementation IEnumConnectionsImpl
- IEnumConnections
interface implementation IConnectionPointImpl
- IConnectionPoint
interface implementation ComContainerEnumAdapterBase
- Core IEnumXXX
functionality suitable for use with the containers from the C++ standard library template <typename> ComContainerEnumAdapter
- Generic copy from container template <typename> ComContainerEnumAdapter<containertype,variant>
- Copy from container with value_type of Variant template <typename> ComContainerEnumAdapter<containertype,connectdata>
- Copy from container with value_type of ConnectData template <typename> ComContainerEnumAdapter<containertype,iunknown*>
- Copy from container with value_type of Unknown template <typename> ComContainerEnumAdapter<containertype,lpconnectionpoint>
- Copy from container with value_type of ConnectionPoint template <typename> ComContainerEnumAdapter<containertype,lpolestr>>
- Copy from container with value_type of String template <typename> class ComEnum
- IEnumXXX
implementation template ComEnumConnections
- Combines ComEnum
, ComContainerEnumAdapter
& IEnumConnectionsImpl
, implementing IEnumConnections
on a std::vector<ConnectData>
ComEnumConnectionPoints
- Combines ComEnum
, ComContainerEnumAdapter
& IEnumConnectionPointsImpl
, implementing IEnumConnectionPoints
on a std::vector<ConnectionPoint>
ComEnumUnknown
- Combines ComEnum
, ComContainerEnumAdapter
& IEnumUnknownImpl
, implementing IEnumUnknown
on a std::vector<Unknown>
ComEnumVARIANT
- Combines ComEnum
, ComContainerEnumAdapter
& IEnumVARIANTImpl
, implementing IEnumVARIANT
on a std::vector<Variant>
ComEnumString
- Combines ComEnum
, ComContainerEnumAdapter
& IEnumStringImpl
, implementing IEnumString
on a std::vector<String>
- 18th November, 2012 - Added new classes:
Timer
- a UI timer class Commandline
- a commandline
utility class - breaks up a string
into a command and its arguments, optionally expanding environment variables and wildcards Path
- a path management utility class
- 21. of November, 2012 - Added new examples:
ButtonExample
DropDownButtonExample
HeaderControlExample
Each of the examples are "full" Windows C++ applications, in the sense that they illustrate program initialization, event handling and painting in less than a 100 lines of C++ code.
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 a std::shared_ptr<HeaderControlForm>
which is why we have a separate initialization function, as the construction of the HeaderControlForm
object and the initial std::shared_ptr<HeaderControlForm>
holding a strong reference the HeaderControlForm
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 the HarlinnWindows
solution.
- 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