HotProp control

HotProp control

Introduction

The HotProp Control was designed for applications that need a flexible, modeless property window. There are many traditional solutions, similar to the property list in Visual Basic (and Visual Studio .NET). HotProp has a much more modern look, and it has also an improved functionality. You don't have to select an item and click once again to open the combo or edit the property. HotProp doesn't have any focus and item selection, instead it uses the hot tracking technique. A border is drawn when you place the mouse above an item and you can open the popup list, toggle the item or start in-place editing with just a single mouse click. The control also supports keyboard shortcuts for each property.

Since version 1.10, the control works in two modes: double-line and single-line. In double-line mode, values of properties are displayed below their headers. In single-line mode, they are displayed to the right, like in traditional property lists.

Types of properties

There are six types of properties available:

Features

Each property has an identifier. It can be any number, provided that it's unique for each property displayed at the same time. The identifier is passed as the first argument to most of the functions. The name of the property is what is displayed on its bar, it can have an underscored mnemonic marked with the '&' sign. The control has an image list, images can be displayed to the left of each property's name.

Properties have two values: a number and a string, connected to each other. The string is always what is displayed in the control. The numeric value is 0 or 1 for a toggle, index of the selected option for a list and the entered value for an integer propetry. When the numeric value is set, the string is updated automatically (either to display proper option for a toggle/list, or the formatted number). A negative value can be given, causing the string to be cleared (it's meant to be an undefined state of a property).

Properties can be added at the bottom or inserted after a specified one. You can remove individual properties or all at once. Remember that these operations don't update the control to avoid flickering when you remove and add many properties. You have to call Update after you add or remove properties to let the control update its size and redraw itself. You don't have to call it when you change properties (name, value, state etc.). The name and image index of each property can be changed.

Each type of property has specific information that you can change using the following functions:

Note that the value of the property is not changed automatically when you call these functions.

Another useful feature of HotProp is the ability to lock or disable individual properties. A locked property is read-only and a small padlock icon appears to the right of its bar. When a property is disabled, its name and icon is grayed and no value is displayed.

In version 1.6, some basic grouping abilities have been added. You can hide and show individual properties and whole ranges of properties instead of creating and removing them every time. Hidden properties still remember their values and can be modified. Some properties may have a special "bar" style, useful for separating groups of items.

Using the control

Project & settings

Add the following files to your project:

Linking msimg32.lib is no longer needed. This library is now loaded dynamically for compatibility with previous Windows versions.

Since version 1.10, visual settings can be changed dynamically. You can enable or disable flat style (which looks like MS Office XP), gradient fill and pop-up list animation.

Using in a splitter window

The simplest way of creating the HotProp control is to create a static splitter window with the application's view in one pane and the control in the other pane. You can use my CDualSplitWnd class which extends the standard splitter window. It can be used in static mode only and can contain two panes. It displays the contents of views when dragging instead of a black stripe (depending on system settings) and uses standard system cursors. Also, panes are resized proportionally when the window size is changed.

1. Add the splitter window to CMainFrame. You can use the original CSplitterWnd instead of my class.

   CDualSplitWnd m_wndSplitter;

2. Create the OnCreateClient virtual function to create static splitter, like this:

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
   if (!m_wndSplitter.CreateStatic(this, 1, 2))
      return FALSE;

   m_wndSplitter.SetInitialRatio(0.75); // left pane is 75% wide

   if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CHotPropView),
      CSize(100, 100), pContext)
      || !m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CHotPropCtrl),
      CSize(100, 100), pContext))
   {
      m_wndSplitter.DestroyWindow();
      return FALSE;
   }

   return TRUE;
}

3. You can add a public method to CMainFrame for easy access to the HotProp control from your view:

CHotPropCtrl& CMainFrame::GetHotProp()
{
   return *((CHotPropCtrl*)m_wndSplitter.GetPane(0, 1));
}

4. Since the control is not a view, the framework doesn't destroy it automatically. You have to do it manually, e.g. in OnDestroy:

void CMainFrame::OnDestroy()
{
   GetHotProp().DestroyWindow();

   CFrameWnd::OnDestroy();
}

5. Once the control is created, you need to initialize the image list. It's best to do it in main frame's OnCreate. The images should be 16 x 16 pixels. You can call GetImageList and initialize the list manually or use the following function to load the bitmap:

   GetHotProp().LoadImages(IDB_IMAGES, RGB(255,0,255));

Using in a modeless window

You can create the HotProp control as a child of a modeless dialog, a property page, docking control bar etc. All you have to remember is that the CHotPropCtrl object has to be allocated on the heap, since it deletes itself in PostNcDestroy.

1. Add a pointer to the parent window's class:

   CHotPropCtrl* m_pHotProp;

2. Add the following code to your window's OnCreate or OnInitDialog:

   m_pHotProp = new CHotPropCtrl;
   m_pHotProp->CreateEx( WS_EX_STATICEDGE, NULL, NULL,
      WS_VISIBLE | WS_CHILD, CRect(...position...), this, ...control ID...);

In this case, the control is destroyed automatically with the parent window. Don't delete it manually.

Interaction with the view

Since version 1.10, you may set the window that will receive notification messages from the control. By default, they are sent to the control's parent window. If the control is located in a splitter window, it's useful to set the receiver to the view window. Notifications are sent whenever a property is changed by the user (or a button property is clicked).

1. Call SetWndToNotify to set the receiver of the notifications. For example:

   GetHotProp().SetWndToNotify(m_wndSplitter.GetPane(0, 0));

2. Manually create the message handler to your view or any window that will receive notifications.

In the header file:

   void OnHotPropUpdate(int nID, int nValue);

Add the following entry to the message map:

   ON_MESSAGE(HPCM_UPDATE, OnHotPropUpdate)

3. Implement OnHotPropUpdate, which gets the identifier of the property that was changed and its new numeric value. You can safely modify any properties in the message handler. If you add or remove properties, don't forget about calling Update.

void CYourView::OnHotPropUpdate(int nID, int nValue)
{
   CHotPropCtrl& prop = ((CMainFrame*)AfxGetMainWnd())->GetHotProp();

   // apply changes to the document
}

4. To handle keyboard mnemonics, you can create a WM_CHAR handler in your view's class. Pass the character to the ProcessKey function.

void CYourView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
   CHotPropCtrl& prop = ((CMainFrame*)AfxGetMainWnd())->GetHotProp();
   prop.ProcessKey(nChar);
}

Custom drawn properties

Properties of all types can be custom drawn. It means that instead of drawing the text value, HotProp calls the DrawCustomItem virtual function. You can override it in your class to implement drawing. By default, it sends a HPCM_DRAWCUSTOM notification to the receiver window (or parent window if the receiver was not specified).

Use SetCustomProp to make a property custom drawn. You should give it a non-zero code, that is passed to your drawing procedure. Giving it a code of 0 restores the standard way of drawing. If several properties are drawn the same way, you can assign them the same code. You should only draw the value itself - the control handles the background and frames for you. You can use GetImageList if you want to draw images.

Custom drawn properties can be used with lists - the items are drawn using the same function. If you don't need a string array associated with the property, you can use the enum type instead of a list. Just set the number of entries and draw whatever you need.

1. Create the message handler just like the one for the update notifications:

In the header file:

   void OnHotPropDrawCustom(int nCustom, CHotPropCtrl::CustomItem* pItem);

Add the following entry to the message map:

   ON_MESSAGE(HPCM_DRAWCUSTOM, OnHotPropDrawCustom)

2. Implement OnHotPropUpdate, which gets the custom code and a structure containing the DC, item rectangle, identifier and values to use. They are not always the current values of the property - the list uses custom drawing on all items.

void CYourView::OnHotPropDrawCustom(int nCustom, CHotPropCtrl::CustomItem* pItem)
{
   CHotPropCtrl& prop = ((CMainFrame*)AfxGetMainWnd())->GetHotProp();
   CDC& dc = *pItem->m_pDC;
   CRect rcItem = pItem->m_rcRect;

   switch (nCustom)
   {
      // draw item
   }
}

Version history

Version 1.11 (February 18, 2004)

Version 1.10 (January 14, 2004)

Version 1.9 (September 5, 2003)

Version 1.8 (September 1, 2003)

Version 1.7 (March 6, 2002)

Version 1.6

Version 1.5

Version 1.4 (January 24, 2002)

License

HotProp Control, version 1.10 (January 14, 2004)
Copyright (C) 2002-2004 Michal Mecinski.

You may freely use and modify this code, but don't remove this copyright note.

THERE IS NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, FOR THIS CODE. THE AUTHOR DOES NOT TAKE THE RESPONSIBILITY FOR ANY DAMAGE RESULTING FROM THE USE OF IT.