Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Windows Development in C++, Working with Menus

4.96/5 (60 votes)
3 Jan 2015CPOL19 min read 179.5K   4.1K  
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.

Image 1

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:

C++
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:

C++
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:

C++
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.

C++
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 “FileSubMenuItem 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 “EditSubMenuItem 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.

Image 2

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:

Image 3

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.

Image 4

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.

Image 5

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...

C++
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...

C++
HWIN_EXPORT virtual void DoOnMenuCommand(Message& message);

...function. The Message class is derived from the Windows tagMSG structure...

C++
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...

C++
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.

C++
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:

C++
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.

C++
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:

C++
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:

C++
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>.

C++
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.

C++
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.

C++
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>.

C++
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:

C++
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.

C++
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:

C++
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.

C++
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.

C++
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.

C++
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.

C++
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.

C++
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:
    C++
    template<typename T>
     T As() const
    {
    ...
    }
    which provides a nice alternative to QueryInterface, assuming unk is an Unknown object:
    C++
    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.

    C++
    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:
    C++
    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.

    Image 6

    The class declaration for the above form is pretty simple:

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

    In the constructor, we just set title for the form:

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

    While the rest of the initialization is done in the DoOnInitialize() function.

    C++
    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:

    C++
    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:

    C++
    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:

    C++
    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

      Image 7

    • CheckBoxExample

      Image 8

    • CommandButtonExample

      Image 9

    • DropDownButtonExample

      Image 10

    • HeaderControlExample

      Image 11

    • LabelExample

      Image 12

    • LinkLabelExample

      Image 13

    • RadioButtonExample

      Image 14

    • TextEditExample

      Image 15

    • 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)