Objective Toolkit : Chapter 15 User Interface Extensions : Workspace Manager
Workspace Manager
Objective Toolkit’s workspace manager classes provide a flexible framework that includes menu and dialog resources for saving and restoring window configurations. You can save and restore workspaces to and from a file or the Win32 registry. The workspace save/restore feature is a user interface enhancement that does not require you to change or redesign your user interface. In addition, you can use the workspace save/restore interface enhancement with Stingray’s MDI, FDI, MTI, or Extended Control Bar Architecture enhancements.
The extended workspace manager now provides a simple alternative to the LoadBarState() and SaveBarState() mechanisms utilized in previous releases of Objective Toolkit for automatically restoring and saving the state of your application’s window configuration.
NOTE >> The extended workspace manager requires you to use the enhanced frame window classes included in Objective Toolkit.
With WDI, the workbook mode must be saved as user data.
There are also limitations in using the SECWorkspaceManagerEx class with FDI applications. For example, there is currently no direct support for saving toolbar information in the FDI child frames.
The Workspace Manager Classes
Objective Toolkit includes two workspace manager classes— SECWorkspaceManager and the more powerful SECWorkspaceManagerEx.
Figure 130 – The Objective Toolkit Workspace Manager Class Hierarchy
The next few sections describe the differences between these two classes.
SECWorkspaceManager versus SECWorkspaceManagerEx
SECWorkspaceManagerEx supports all the features in SECWorkspaceManager and adds the following:
Persistence of workspaces directly to either a file or the registry.
Persistence of multiple views per document.
Persistence of an application’s current activation state.
Workspace versioning.
Full support for Objective Toolkit docking windows, toolbars, and menubars.
Support for Docking Views.
All data persisted by the workspace manager is stored in a tree of SECPersistentTreeNode (PTN) objects. Each object can have an arbitrary number of key-value pairs to represent one piece of data. This model is similar to the windows system registry.
By default, the extended workspace manager stores the application state in a preset tree where settings are grouped logically according to their role. For example, controlbar settings are in one subtree, child frame windows in another, etc. By overriding the appropriate Open() and Save() workspace manager methods, you can easily add your own child PTN objects to be included in the workspace state. See “To add application specific information to the workspace state.”
This class is easier to integrate into your application than the legacy SECWorkspaceManager class. SECWorkspaceManagerEx has been completely redesigned. You can easily extend it with custom configuration information and a custom persistence protocol.
NOTE >> SECWorkspaceManagerEx is fully redesigned, and has many enhancements compared to its predecessor, SECWorkspaceManager. Unfortunately, the two classes are not code compatible with each other. You cannot replace previous instances of SECWorkspaceManager with SECWorkspaceManagerEx. We would like to encourage you to migrate to SECWorkspaceManagerEx in any future development. SECWorkspaceManager is an obsolete class that is still offered only to ensure backward compatibility.
Support for Dynamically Created Controlbars
Unlike the legacy SECWorkspaceManager class, the new SECWorkspaceManagerEx class can now persist controlbars that have been created dynamically (for example, controlbars created using the new operator). The SECWorkspaceManager class can only restore the states of windows that already exist (for example, those created during CMainFrame::OnCreate) whereas the new SECWorkspaceManagerEx class can actually create controlbars based on stored run-time class information and also restore their state.
The extended workspace manager class, SECWorkspaceManagerEx, can also persist the run-time class information corresponding to each controlbar, which can then be used to reconstruct any dynamically created controlbars. This support is virtually seamless to your application. The only additional step required for this support is the implementation of the following two MFC serialization macros for each controlbar class: IMPLEMENT_SERIAL and TOOLKIT_DECLARE_SERIAL. These macros are already included in all the packaged Objective Toolkit SECControlBar-derived classes. Please consult the standard MFC references for more information on using these macros.
Multiple Views per Document, Multiple Views Per Frame
The extended workspace manager has prebuilt support for multiple views tied to a single document when each view occupies its own frame window. This applies to Docking Views as well.
If you have multiple views sharing a single frame window (for example, inside a splitter or tab window), the workspace manager cannot predict how you want these additional views to be restored (this is an application specific function). What it can do, however, is supply the appropriate virtual overrides for you to hook in the application specific information required for this procedure. See SECWorkspaceManagerEx::SaveAdditionalViewPerFrame() and SECWorkspaceManagerEx::OpenAdditionalViewPerFrame() for more information. The workspace manager automatically stores the presence of multiple views per frame so that OpenAdditionalViewPerFrame() is automatically called with the appropriate information. It is your responsibility to supply any additional state information, and act upon that state information (for example, inserting a new tab, creating a new splitter, etc.).
Using SECWorkspaceManagerEx
The following sections describe how to use SECWorkspaceManagerEx in a variety of application types.
To add workspace management to your application
1. Include support for Objective Toolkit docking windows.
2. At the end of your CMainFrame::OnCreate() handler, add a call to the InitWorkspaceMgrEx() method. For example:
 
InitWorkspaceMgrEx(
_T("Software\\MyCompany\\Sample\\v1.0"), FALSE);
The first parameter is a base registry key off HKEY_CURRENT_USER to store your workspace state information. This key must be unique to your application and its version number. The second parameter toggles the workspace manager in registry mode. If TRUE, the workspace manager saves workspace state information to a registry key off the path specified in the first parameter. If FALSE, the workspace manager prompts you to save/load workspace information from a file. The registry key is still used, but it only contains a list of Most Recently Used workspace paths.
3. If you’d like, you can add the following commands to your pull-down menus to hook into the workspace manager functionality automatically.
 
ID_WORKSPACE_NEW
ID_WORKSPACE_SAVE
ID_WORKSPACE_SAVEAS
ID_WORKSPACE_OPEN
ID_WORKSPACE_DELETE
ID_WORKSPACE_1 (automatic insertion of “Recent Workspaces” list)
To load and store the workspace state automatically
1. Follow the steps described in “To add workspace management to your application”.
2. In your application’s InitInstance() method, obtain a pointer to the workspace manager with the GetWorkspaceMgrEx() method and then call the OpenWorkspace() method. Insert this code after the code that initializes the frame but before the frame is made visible. For example:
 
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;m_pMainWnd = pMainFrame;
 
SECWorkspaceManagerEx* pWsMgr = pMainFrame->GetWorkspaceMgrEx();
if (pWsMgr)
pWsMgr->OpenWorkspace(TRUE); // TRUE = open MRU,
// FALSE = prompt
OpenWorkspace() can take a TRUE or FALSE parameter. If TRUE, the workspace manager automatically loads the name of the last stored workspace. If FALSE, a dialog is displayed that asks the user which workspace to load.
3. In your CMainFrame::OnClose(), save the workspace using the SaveWorkspace() method.
 
SECWorkspaceManagerEx* pWsMgr=GetWorkspaceMgrEx();
ASSERT(pWsMgr);
pWsMgr->SaveWorkspace(_T(".\\SdiMgr.wsp"));
GetWorkspaceMgrEx() fetches the stored value from the SEC frame window. The SaveWorkspace() call uses the appropriate parameter based on the current storage mode (registry vs. file mode). Please refer to SECWorkspaceManagerEx::SaveWorkspace() in the Objective Toolkit Class Reference for more information.
To add application specific information to the workspace state
1. Follow the steps described in “To add workspace management to your application”.
2. Derive a class from SECWorkspaceManagerEx. Ensure that your derived class has properly implemented the TOOLKIT_DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros for run-time CObject instantiation.
3. Specify the run-time class of your SECWorkspaceManagerEx-derived class as a parameter passed to the InitWorkspaceManagerEx() call in CMainFrame::OnCreate(). For example:
 
InitWorkspaceMgrEx(
_T("Software\\Stingray\\Samples\\Toolkit\\WMCust\\v1.0"),
TRUE, RUNTIME_CLASS(CCustomWrkspcEx));
4. Create an override for the SaveWorkspaceCustomData() method.
 
// Override SaveWorkspaceCustomData to save data
// on a per-workspace basis
void CCustomWrkspcEx::SaveWorkspaceCustomData (
SECPTNFactory& nodeFactory,
SECPersistentTreeNode* pRoot,
const CString&
strWorkspaceName)
{
 
BOOL bCustomDataSetting =
GetTheCustomDataSomehow();
// The pRoot PTN object passed in is the
// appropriate parent for this custom data.
// Now create a child node to store additional
// information...
SECPersistentTreeNode* pCustomNode =
nodeFactory.CreateNode(
_T("MyCustomDataNode"),pRoot);
 
ASSERT(pCustomNode);
 
// And add the custom data. This
// will automatically
// be saved by the recursive save
// operation initiated
// by the workspace mgr - no additional
// coding required. pCustomNode->AddKeyValueBool(
_T("MyUniqueCustomKeyName"),
bCustomDataSetting);
 
// We could store more key-value pairs
// here if we wanted.
// The only restriction is // that each key must have a unique name.
// Invoke the base...
SECWorkspaceManagerEx::SaveWorkspaceCustomData(
nodeFactory,pRoot,strWorkspaceName); }
5. Create an override for the OpenWorkspaceCustomData() method.
 
void CCustomWrkspcEx::OpenWorkspaceCustomData(
SECPersistentTreeNode* pRoot,
const CString&
strWorkspaceName)
{
 
// Locate the custom data PTN node
// stored above...
SECPersistentTreeNode* pCustomNode=pRoot->
FindChildNode(_T("MyCustomDataNode"));
 
// it must be there, unless the workspace
// was corrupt or not versioned.
ASSERT(pCustomNode);
 
// Now, load the custom data stored in this
// object (again, we could load multiple
// key-values here if they were stored...)
BOOL bCustomDataSetting;
pCustomNode->GetKeyValueBool(
_T("MyUniqueCustomKeyName"),
bCustomDataSetting);
 
// Got it, do something
DoSomethingWithTheData(bCustomDataSetting);
 
// Invoke the base...
SECWorkspaceManagerEx::OpenWorkspaceCustomData(
pRoot,strWorkspaceName);
}
6. To store workspace data at a finer level of granularity, you can override other SECWorkpsaceManagerEx functions. For example, by overriding OpenSpecificChildFrame() and SaveSpecificChildFrame(), you can store data on a per-view basis. This is useful if you have multiple views tied to a single document and you want to store presentation information for each of those additional views that does not logically fit into the document data stream.
To store the WDI workbook state in the workspace state
1. Follow the steps described in “To add workspace management to your application” that pertain to the creation of the stub method for the SaveWorkspaceCustomData() and OpenWorkspaceCustomData() overrides.
2. In the SaveWorkspaceCustomData() override, query the workbook state and save it as one of the tree nodes in the workspace state. For example:
 
void CCustomWrkspcEx::SaveWorkspaceCustomData(
SECPTNFactory& nodeFactory,
SECPersistentTreeNode* pRoot,
const CString&
strWorkspaceName)
{
 
// library should insure parameters are valid
ASSERT(pRoot);
 
// Just for purposes of demonstration,
// we are saving the current
// workbook mode setting
 
// Get the workbook mode setting
SECWorkbookWnd* pWB=(SECWorkbookWnd *)AfxGetMainWnd();
ASSERT(pWB);
ASSERT_KINDOF(SECWorkbookWnd,pWB);
BOOL bWorkbookMode=pWB->m_bWorkbookMode;
 
// Create a custom child node for data storage.
// Note, all workspace data is organized in
// a tree of these SECPersistentTreeNode
// objects,so we can insert new nodes anywhere
// in the tree, as long as we also override
// the corresponding "Open" function to act
// upon this additional data on workspace load.
SECPersistentTreeNode* pCustomNode =
nodeFactory.CreateNode(
szCustomWorkspaceDataNode,pRoot);
ASSERT(pCustomNode);
 
// And add the custom data. This will
// automatically be saved by the recursive save
// operation initiated by the workspace mgr
// – no additional coding required.
pCustomNode->
AddKeyValueBool(
szWorkbookModeKey,bWorkbookMode);
 
// As of Objective Toolkit 5.2, this is a no-op, but it's
// still a good practice to get into...
SECWorkspaceManagerEx::SaveWorkspaceCustomData(
nodeFactory,pRoot,strWorkspaceName);
}
3. In the OpenWorkspaceCustomData(), restore the saved workbook state from the data in the workspace state.
 
void CCustomWrkspcEx::OpenWorkspaceCustomData(
SECPersistentTreeNode* pRoot,
const CString& strWorkspaceName)
{
 
// Get the workbook mode setting stored by
// SaveWorkspaceCustomData
 
// First, locate the child custom data node
SECPersistentTreeNode* pCustomNode =
pRoot->
FindChildNode(
szCustomWorkspaceDataNode);
 
// if not, corrupt workspace!
ASSERT(pCustomNode);
 
// And load the data value in this node
// (note that 1 PTN node can have an
// arbitrary number of
// key-value data pairs, much like how
// the registry tree works.
// In this example, there's only one key-value
// pair).
BOOL bWorkbookMode;
pCustomNode->
GetKeyValueBool(
szWorkbookModeKey,bWorkbookMode);
 
// Setting loaded. Now apply the setting to the
// workbook
SECWorkbookWnd* pWB=(SECWorkbookWnd *)AfxGetMainWnd();
ASSERT(pWB);
ASSERT_KINDOF(SECWorkbookWnd,pWB);
pWB->SetWorkbookMode(bWorkbookMode);
// As of Objective Toolkit 5.2, this is a no-op, but it's still
// a good practice to get into...
SECWorkspaceManagerEx::OpenWorkspaceCustomData(
pRoot,strWorkspaceName);
}
Using SECWorkspaceManager
The SECWorkspaceManager class is provided only for backward compatibility. You are encouraged to use SECWorkspaceManagerEx for all new development.
To add workspace management to your application:
1. Add an SECWorkspaceManager pointer (for example, m_pWorkspaceMgr) to your application class or create it as a global variable if you prefer.
2. In CMyApp::OnInitInstance(), instantiate and create the SECWorkspaceManager instance, storing a pointer to it in m_pWorkspaceMgr. For example:
 
BOOL CIDEApp::InitInstance()
{
LoadStdProfileSettings();
 
m_pDocTemplate = new CMultiDocTemplate(
IDR_SOURCE_EDITOR,
RUNTIME_CLASS(CSrcDoc),
RUNTIME_CLASS(CSrcFrame),
RUNTIME_CLASS(CSrcView));
AddDocTemplate(m_pDocTemplate);
 
// Instantiate the workspace manager
m_pWorkspaceMgr = new SECWorkspaceManager();
m_pWorkspaceMgr->Create(AfxGetApp(),
CString("Software\\YourApp\\Workspaces\\")
// …
}
3. Override OnCmdMsg() in your application class as follows:
 
BOOL CMyApp::OnCmdMsg(UINT nID, int nCode,
void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (CWinApp::OnCmdMsg(nID, nCode,
pExtra, pHandlerInfo))
return TRUE;
 
// otherwise check the workspace manager
if (m_pWorkspaceMgr != NULL &&
m_pWorkspaceMgr->OnCmdMsg(nID, nCode,
pExtra, pHandlerInfo))
return TRUE;
 
return FALSE;
}
4. Add the windows to the active workspace when they’re created and then remove them from the workspace when they’re destroyed. To do this, override the CFrameWnd::LoadFrame() and CFrameWnd::DestroyWindow() methods as follows:
 
BOOL CMyChildFrame::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
BOOL bRetval =
CMDIChildWnd::LoadFrame(nIDResource,
dwDefaultStyle, pParentWnd, pContext);
theWorkspaceManager.AddWindow(this);
return bRetval;
}
 
BOOL CMyChildFrame::DestroyWindow()
{
theWorkspaceManager.RemoveWindow(this);
return CMDIChildWnd::DestroyWindow();
}
5. Include the secres.h and secres.rc files in your application’s resource file. To do this, choose Resource Includes… from the Visual Studio View menu and add secres.h to the middle edit box and secres.rc to the bottom edit box. By doing this, you ensure that all resource IDs are defined for the workspace menu created in the next step.
6. Add the Workspace menu to your application’s menu. You can make the workspace menu a standalone menu or a popup from the File menu if you prefer. Regardless of where you choose to place the workspace menu, use the workspace resource IDs defined in secres.h. They are:
 
ID_WORKSPACE_NEW
ID_WORKSPACE_SAVE
ID_WORKSPACE_SAVEAS
ID_WORKSPACE_OPEN
ID_WORKSPACE_DELETE
ID_WORKSPACE_CLOSE
ID_WORKSPACE_1 through ID_WORKSPACE_8
Workspace Manager Samples
The Objective Toolkit WrkSpcEx sample (<stingray-installdir>Samples\Toolkit\MFC\Docking\WrkspMgr\WrkSpcEx) illustrates the use of the SECWorkspaceManagerEx class and its supporting menu and dialogs.
Also refer to the sample SdiMgr for an example of the workspace manager used programmatically (no dialogs or command handlers) for persistent state in a SDI application. This sample does not ship with the product. For information on how to obtain this sample, see “Location of Sample Code” in the Getting Started part.