Professional UI Solutions
Site Map   /  Register
 
 
 

Prof-UIS Customization Subsystem

The information in this article applies to Prof-UIS version 2.23 or later

General Information

The Prof-UIS customization subsystem enables developers to create applications featuring extremely customizable user interface by providing them with a mechanism for managing toolbars, menus and keyboard accelerators in a centralized way. That is primarily based on the Command Tree and Customize Site objects, which are implemented by the CExtCustomizeCmdTreeNode and CExtCustomizeSite classes correspondingly.

Like in Win32's HMENU, a command tree in Prof-UIS is a system of nested command groups. A command group in its turn is used for structuring menus and toolbars, as well as command groups displayed on the Commands page in the Customize dialog. Basic properties of each command are stored in the corresponding profile of the command manager. Extended properties specifying the current state of toolbar buttons and menu items are stored in the command trees. Each tree item describes an ordinary command, submenu, color selection command, or text/combobox field. Such a tree item contains the following command properties:

  • Flags identifying the command type
  • Basic (or persistent) command identifier, which is invariable and is used as a unique identifier
  • Effective command identifier specifying the current command of a toolbar button. Unless the toolbar button has a submenu, it has the same value as the basic command identifier does. For toolbar buttons with a submenu, it is specified with the current selected command in the submenu
  • Menu text specifying the current menu text; it can be changed in the Customize mode
  • Toolbar text specifying the current toolbar text; it can be changed in the Customize mode
  • User defined text related to the command
  • User defined LPARAM value related to the command
  • Customized icon image, which is the CExtCmdIcon object used for storing an image created by the user. In case the user does not edited the icon related to the command, the corresponding image stored in the profile of the command manager is used
  • Text and/or combo field metrics specifying the size of the input field
NOTE: Text information for keyboard accelerators is not a property of the command tree node. It is separately stored in Customize Site.

The command tree item related to a submenu also has its own command identifier, which is never used in WM_COMMAND messages. This identifier distinguishes its command tree item from all others. Such command identifiers are created automatically during initialization of the customization subsystem.

Both edit and combobox fields in toolbars and menus are built in components of the customization subsystem and have nothing to do with the CExtComboBox and CExtEdit objects.

Each menu item and toolbar button is associated with two nodes of different command trees: the first one describes the initial state of the item/button and all its child commands and the second is responsible for the current state. Such a parallel data structure is used to implement the Reset command that is available for any command in its context menu.

Customize Site implements all data and methods required for customizing the application's user interface. This subsystem requires OLE to be initiated first, since it uses the standard OLE drag-n-drop interface. That can be done by calling the AfxOleInit() global function in the InitInstance() method of the class derived from CWinApp. Customize Site controls the following data groups:

  • Persistent and user defined toolbars
  • Document/view menus and accelerator tables
  • Command categories
  • Global UI options

Persistent toolbars are initiated in the OnCreate() method of the frame window before the Customize Site object is initiated. Such toolbars cannot be removed or renamed when in the customize mode. To have a unique name for each toolbar is considered a good style.

Customize Site stores the initial and current state of the menu and accelerator table registered for each document and/or view. In the Customize mode, the user can select any document available and customize the menu and/or accelerator table associated with it. All the data related to the document's menu and accelerators are encapsulated in the CExtCustomizeSite::CCmdMenuInfo auxiliary class. One of its instances serves as a descriptor of the default menu in any MDI application. In case the SDI application, only one descriptor (i.e., of the default menu) is used and the user cannot select other menus and keyboard accelerators in the Customize dialog. Customize site automatically renews inner tables with accelerators in case of any changes and that allows menu items and tooltips for toolbar buttons to display the correct info on hot key combinations assigned to commands.

Command categories are intended to help the user find the commands during customization. They are displayed on the Commands page in the Customize dialog.

Global UI options can be found on the last page of the Customize dialog and allows the user to set global parameters for menus, toolbars and tooltips as well as to reset commands usage data that controls unfolding menus.

How To Implement It?

Below are typical steps the developer should follow when creating an application featuring Prof-UIS customization.

The initialization of the frame is performed in the OnCreate() method of the main frame window:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
		. . .

In case of the SDI application, the view is created.

The profile of the command manager is set up and the commands are registered in it:

CWinApp * pApp = ::AfxGetApp();
ASSERT( pApp != NULL );
ASSERT( pApp->m_pszRegistryKey != NULL );
ASSERT( pApp->m_pszRegistryKey[0] != _T('\0') );
ASSERT( pApp->m_pszProfileName != NULL );
ASSERT( pApp->m_pszProfileName[0] != _T('\0') );
ASSERT( pApp->m_pszProfileName != NULL );

VERIFY(
g_CmdManager->ProfileSetup(pApp->m_pszProfileName, 
	GetSafeHwnd() )
&& g_CmdManager->UpdateFromMenu( pApp->m_pszProfileName, 
	IDR_MAINFRAME )
&& g_CmdManager->UpdateFromMenu( pApp->m_pszProfileName, 
	IDR_any_other_menu_resource )
&& g_CmdManager->UpdateFromToolBar( pApp->m_pszProfileName, 
	ID_any_toolbar_resource )
);

All control bars are created, frame windows and docking bars are set up, and command usage statistics is loaded:

m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBarStandard.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBarUiLook.EnableDocking(CBRS_ALIGN_ANY);

if( !CExtControlBar::FrameEnableDocking(this) )
{
	ASSERT( FALSE );
	 return -1;
}

VERIFY(
	g_CmdManager->SetBasicCommands(
	pApp->m_pszProfileName,
	g_statBasicCommands
	)
);
g_CmdManager->SerializeState(
	pApp->m_pszProfileName,
	pApp->m_pszRegistryKey,
	pApp->m_pszProfileName,
	false
);

The default menu in case of the MDI application or only one menu in case of the SDI application is registered:

VERIFY(
	CExtCustomizeSite::MenuInfoAdd(
	this,
	_T("Default"),
	IDR_MAINFRAME,
	true
	)
);

VERIFY(
	CExtCustomizeSite::MenuInfoLoadAccelTable(
	_T("Default"),
	IDR_MAINFRAME
	)
);

All documents of the MDI application are registered:

VERIFY(
	CExtCustomizeSite::MenuInfoAdd(
	this,
	_T("Document"),
	IDR_DOC_TYPE,
	true
	)
);

VERIFY(
	CExtCustomizeSite::MenuInfoLoadAccelTable(
	_T("Document"),
	IDR_MAINFRAME or IDR_DOC_TYPE_ACCEL_TABLE
	)
);

The necessary settings can be made for command trees and menus. At this step, the developer can change the structure of any tree, register some text/combobox fields or color commands, modify keyboard accelerator tables, and etc. For example, to reinitialize a registered menu and set its initial state, you should do the following:

CExtCustomizeCmdTreeNode * pMenuNodeInitial =
 CExtCustomizeSite::MenuInfoGetByName( _T("Default") ) ->GetNode( true );
CExtCustomizeCmdTreeNode * pMenuNodeCustomized =
 CExtCustomizeSite::MenuInfoGetByName( _T("Default") ) ->GetNode( false );
// changing command tree structure of the pMenuNodeInitial node 
. . .
  // reset initially customized menu state:
	*pMenuNodeCustomized = *pMenuNodeInitial;

Customize Site is initialized with data associated with this frame window. The necessary commands are registered in it:

if( !CExtCustomizeSite::EnableCustomization(
	this,
	__ECSF_DEFAULT
	))
{
	ASSERT( FALSE );
	return -1;
	}

	CExtCustomizeSite::CategoryUpdate( IDR_MAINFRAME );
	CExtCustomizeSite::CategoryMakeAllCmdsUnique();
	CExtCustomizeSite::CategoryAppendAllCommands();
	CExtCustomizeSite::CategoryAppendNewMenu();
}

Persistent states of the customization subsystem and control bars are restored. Typically, these operations are final in the OnCreate() method of the frame window:

CExtCustomizeSite::CustomizeStateLoad(
	pApp->m_pszRegistryKey,
	pApp->m_pszProfileName,
	pApp->m_pszProfileName
	);

if( !CExtControlBar::ProfileBarStateLoad(
	this,
	pApp->m_pszRegistryKey,
	pApp->m_pszProfileName,
	pApp->m_pszProfileName,
	&m_dataFrameWP
	))
{
		DockControlBar( &m_wndMenuBar );
		DockControlBar( &m_wndToolBarStandard );
		. . .
		RecalcLayout();
}

	return 0;
}

Finally, you should put the code for storing the UI state into the DestroyWindow() method of the main frame window:

BOOL CMainFrame::DestroyWindow() 
{
CWinApp * pApp = ::AfxGetApp();
	ASSERT( pApp != NULL );
	ASSERT( pApp->m_pszRegistryKey != NULL );
	ASSERT( pApp->m_pszRegistryKey[0] != _T('\0') );
	ASSERT( pApp->m_pszProfileName != NULL );
	ASSERT( pApp->m_pszProfileName[0] != _T('\0') );

	VERIFY(
		CExtCustomizeSite::CustomizeStateSave(
			pApp->m_pszRegistryKey,
			pApp->m_pszProfileName,
			pApp->m_pszProfileName
			)
		);

	VERIFY(
		CExtControlBar::ProfileBarStateSave(
			this,
			pApp->m_pszRegistryKey,
			pApp->m_pszProfileName,
			pApp->m_pszProfileName
			)
		);
	VERIFY(
		g_CmdManager->SerializeState(
			pApp->m_pszProfileName,
			pApp->m_pszRegistryKey,
			pApp->m_pszProfileName,
			true
			)
		);
	g_CmdManager->ProfileWndRemove( GetSafeHwnd() );
	
	return CFrameWnd::DestroyWindow();
}
Back To Top Other Articles...