Taking ControlAdding controls to a window can be accomplished in a variety of ways. One of the easiest (and fastest) ways is to use a dialog template with a CFormView class. A dialog template is a resource file that is generated by the resource editor when you layout controls for a dialog. A CFormView class allows you to add a dialog template to a window by acting like a modeless dialog box imbedded into the window. A modeless dialog box is just a dialog that functions coincidentally with the main frame. A modal dialog box is the opposite, forcing the user to do what is necessary to close the box before the main frame becomes active again. Creating the Dialog TemplateUsing the resource editor create a new dialog template with four dropdown lists arranged in a square. Each of these lists will represent a viewport and allow the user to change between a front, side, top, and perspective view. Do yourself a favor and change the control IDs to meaningful names, it will make life easier later on. Since these will be static, read-only dropdown lists go ahead and enter the data for the dropdown lists (Front, Top, Side, Persp). Just a side note, there are a few aspects of making dropdown lists with the resource editor that drove me nuts, they weren’t documented very well and were quite annoying. To enter a new line in the data field you need to press Ctrl + Enter, and you must also set the dropdown height by clicking on the arrow for the list. Make sure the ordering is the same for each list.
Creating the Form View ClassDouble click on the dialog template in the resource editor. This should open the ClassWizard with a message regarding your new template. You can also do this manually from within the ClassWizard if you don’t get a message for whatever reason. Create a new class that inherits from CFormView and uses your new dialog template (the dialog ID is selected in the dropdown list under the parent class). We’ll call this new class CInfoPannel for the rest of this article. Go back to MainFrm.cpp and change the class of the remaining window to CInfoPannel. Run the program and make sure you see the controls in the panel as expected. So far so good. Now if only it did something... Adding Member Variables and Message MapsIn a normal dialog box controls are maintained by creating special member variables that are automatically updated with validated data from their associated controls. Changing those same member variables can change the controls in turn. Since a CFormView class (and classes that inherit from it) is not a true dialog box we have to take a few extra steps to use this same method. First, just as though we were dealing with a dialog box, we add the member variables using the ClassWizard. This is where it pays off if you used meaningful control IDs earlier. In order for these variables to be updated from their respective controls we have to explicitly call UpdateData, a method that is normally called by the framework to update dialog controls and member variables. By passing this method TRUE, information from a control will be stored in a member variable. By passing it FALSE, information from a member variable will update a control. We assume that we would want to change control values during the class’ WM_UPDATE message map: void CInfoPannel::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { UpdateData( FALSE ); } Finally we need to perform some action when the dropdown selection is changed. By double clicking one of the controls in the resource editor the wizard will add a message map that corresponds to that control to the parent class. Do so for each dropdown list. Now we’re faced with a dilemma. To change our viewports we need to access our viewport splitter. At this point the easiest thing to do is to bend the OOP rules a little bit, although C++ zealots are welcome to flame me into a cinder for it: Where: CSplitterWnd *m_viewports; Is a member variable of CInfoPannel. In CMainFrame, just after CInfoPannel is created: // Setup a pointer to the viewport splitter to be // used later by the information bar to change // viewports. ((CInfoPannel*)(m_mainSplitter.GetPane( 0, 1 )))->m_viewports = &m_viewportSplitter; We’re now able to alter the viewport splitter to our heart’s content, which is done during each dropdown list’s message map: // Message handlers for the info bar // controls. Viewport dropdowns. void CInfoPannel::OnEditchangeUpperleft() { // Automatically update the form view // member variables with the values from // their associated controls. UpdateData( TRUE ); // Delete the view in the appropriate // splitter pane and create a new one // based on the dropdown selection. CRuntimeClass *newViewClass; CRect cr; // client rectange switch ( m_upperLeft ) { case FRONTVIEWPORT: newViewClass = RUNTIME_CLASS( CFront ); break; case TOPVIEWPORT: newViewClass = RUNTIME_CLASS( CTop ); break; case SIDEVIEWPORT: newViewClass = RUNTIME_CLASS( CSide ); break; case PERSPVIEWPORT: newViewClass = RUNTIME_CLASS( CPerspective ); break; } m_viewports->GetClientRect( &cr ); m_viewports->DeleteView( 0, 0 ); m_viewports->CreateView( 0, 0, newViewClass, CSize( cr.Width() / 2, cr.Height() / 2 ), NULL ); // Add the new view to the application's // document. GetDocument()->AddView( (CView*)(m_viewports->GetPane( 0, 0 )) ); // Recalculate the splitter window so the // changes are displayed. m_viewports->RecalcLayout(); } Take minute to wrap your brain around that, there’s a bunch of stuff there. First we call UpdateData( TRUE ) to update the member variables associated with all the controls. Next the appropriate class type is chosen based on the dropdown selection. FRONTVIEWPORT, SIDEVIEWPORT, TOPVIEWPORT, and PERSPVIEWPORT are just constants that correspond to the appropriate dropdown index number. Following that the current view for the appropriate viewport is deleted and a new one is created. The document is then updated with the new view to avoid an issue further down the line. So you run your program and find that, amazingly enough, nothing happens. After some detective work and a bunch of swearing you’ll find that the default behavior for a dropdown message map is to run when a dropdown has been edited (the actual text has been edited, rather than the selection). Since our dropdown lists are read-only our message maps will never be called. This can be fixed simply enough by editing some of the wizard-generated code: Change: BEGIN_MESSAGE_MAP(CInfoPannel, CFormView) //{{AFX_MSG_MAP(CInfoPannel) ON_CBN_EDITCHANGE(IDC_UPPERLEFT, OnEditchangeUpperleft) ON_CBN_EDITCHANGE(IDC_UPPERRIGHT, OnEditchangeUpperright) ON_CBN_EDITCHANGE(IDC_LOWERLEFT, OnEditchangeLowerleft) ON_CBN_EDITCHANGE(IDC_LOWERRIGHT, OnEditchangeLowerright) //}}AFX_MSG_MAP END_MESSAGE_MAP() To: BEGIN_MESSAGE_MAP(CInfoPannel, CFormView) //{{AFX_MSG_MAP(CInfoPannel) ON_CBN_SELCHANGE(IDC_UPPERLEFT, OnEditchangeUpperleft) ON_CBN_SELCHANGE(IDC_UPPERRIGHT, OnEditchangeUpperright) ON_CBN_SELCHANGE(IDC_LOWERLEFT, OnEditchangeLowerleft) ON_CBN_SELCHANGE(IDC_LOWERRIGHT, OnEditchangeLowerright) //}}AFX_MSG_MAP END_MESSAGE_MAP() Run the program one more time and everything should be squared away. Looking good, it almost does something now...
|