Getting StartedCreating the ProjectWhen choosing your project type (before or after you’ve created your workspace, depending on the way you do things) select MFC AppWizard EXE. By choosing the EXE option rather than DLL we make sure we’re creating a standalone application. Choosing this option will launch the AppWizard (hardly surprising). Choose "single document", and accept the defaults. You can turn off "printing and printing preview", just make sure that you’re using the document/view architecture. Compiling the ProjectAfter you’ve completed the wizard you should find a dozen files or so in your workspace. Don’t panic, this is just the beginning! Just take a few deep breaths and relax, they’re mostly class definitions and the like and will become clear with time. Before you continue compile, build and execute your project. MFC uses a precompiled header file, which has to compiled and saved to disk. Making sure you perform a complete build at the beginning of development will help you avoid cryptic link errors later. Are we having fun yet? Creating the LayoutThe first files we will be looking at will be MainFrm.h and MainFrm.cpp which contain your CMainFrame class. Like the name suggests, CMainFrame is the main frame window for your application. While your own applications will most likely spawn dialog boxes and child windows, the main frame is the base of all the action. This is where we need to add our splitter windows. A CSplitterWnd class or splitter window is a way to split the screen into two or more different views or windows. A comparable example might be frames in HTML, or the different panes in Microsoft Developer Studio. A splitter window should be created during the initialization of the client area of the main frame window. In order to facilitate this we need to perform two steps. First our CSplitterWnd objects are added as members of the CMainFrame class. Note that this doesn’t initialize or create them in anyway, it just gives us the option of doing so in the future. Since our layout plan has four viewports bordered by a single pane of controls we need to use two CSplitterWnd objects to accomplish our desired layout. The first splitter will be used to split the screen into a window for the control pane and a window for the viewports. The second splitter will be used to split the viewport window into four separate windows. Again this is similar to creating frames in HTML. Somewhere in the class definition in MainFrm.h we add the following code: CSplitterWnd m_mainSplitter, // Splitter windows m_viewportSplitter; Secondly we must create the splitters when the client area is created. In order to do this we have to add a message map to the CMainFrame class. A message map is the way messages are passed in MFC. Whenever the class that is assigned a message map receives a certain message, it executes a named function with parameters corresponding to the pertinent information in the message. In our case we use the ClassWizard to add a message map to CMainFrame for the OnCreateClient message. Looking in MainFrm.cpp we find the following method skeleton: BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class return CFrameWnd::OnCreateClient(lpcs, pContext); } Not bad, we now have a place to create our splitter windows. Splitter windows come in two flavors, static and dynamic. A dynamic splitter can be split and collapsed by the user, while a static splitter cannot. We’ll only be dealing with static splitters since we generally don’t want to give the user that kind of control over the layout. First we create the main splitter window and check for errors: if ( !m_mainSplitter.CreateStatic( this, 1, 2 ) ) { MessageBox( "Error setting up splitter frames!", "Init Error!", MB_OK | MB_ICONERROR ); return FALSE; } The parameters are fairly straightforward; we pass in the parent window and the number of rows and columns in the splitter. Next we must create the views that we want to fill each pane in the splitter. When creating a static splitter you must create a view or nested splitter window for each pane before it is displayed. First we create the view for our control panel: CRect cr; // client rectangle - // used to calculate client sizes GetClientRect( &cr ); if ( !m_mainSplitter.CreateView( 0, 1, RUNTIME_CLASS(CYourProjectNameView), CSize( INFOBAR_SIZE, cr.Height() ), pContext ) ) { MessageBox( "Error setting up splitter frames!", "Init Error!", MB_OK | MB_ICONERROR ); return FALSE; } This one’s more of a mouthful. The first two parameters are fairly simple - just the row column (zero based) indices of the splitter where the view is to be created. The third parameter is the class type that will be created. The RUNTIME_CLASS statement is a macro that simplifies passing in a dynamically declared class. CYourProjectNameView is the default view that is created by the wizard. We will return to this code later and change this class to our own. The last two parameters are just the size of the pane to be created and the context that was passed into the OnCreateClient method. Next we create the viewport splitter, nested inside the main splitter: if ( !m_viewportSplitter.CreateStatic( &m_mainSplitter, 2, 2, WS_CHILD | WS_VISIBLE, m_mainSplitter.IdFromRowCol( 0, 0 ) ) ) { MessageBox( "Error setting up splitter frames!", "Init Error!", MB_OK | MB_ICONERROR ); return FALSE; } Since this is a nested splitter we have a few more things to worry about. Rather than passing this as the parent window we pass in the address of the parent splitter. Also rather than passing in the context we pass the ID from the pane where we want to insert the splitter. Views must then be created for every pane in this splitter in the exact same manner as before. For now make them all the default view type, we’ll change them later. Note that the skeleton returns the parent class’ OnCreateClient method. This is typical behavior in message maps. Often times you’ll want to poke your head in the works, but afterwards have business proceed as usual. In this particular case this is not what we want however. If we call the parent method we’ll be creating the client area a second time, basically destroying all our hard work. To prevent this just return TRUE rather than the parent method. If you execute your program at this point you should see the splitters in place, more or less how you expected them. However if you try resizing the window you’ll find that the splitters don’t change with your window. Obviously this isn’t behavior we want in our application. This can be fixed by adding another message map to CMainFrame for the WM_SIZE message: void CMainFrame::OnSize(UINT nType, int cx, int cy) { CFrameWnd::OnSize(nType, cx, cy); if ( nType != SIZE_MINIMIZED ) { m_mainSplitter.SetRowInfo( 0, cy, 0 ); m_mainSplitter.SetColumnInfo( 0, cx - INFOBAR_SIZE, 0 ); m_mainSplitter.SetColumnInfo( 1, INFOBAR_SIZE, 0 ); // etc... m_mainSplitter.RecalcLayout(); m_viewportSplitter.RecalcLayout(); } } Try running this and see if you get an error. Whoops, it looks like we get a WM_SIZE message before OnCreateClient is called! To remedy this we add a flag to the CMainFrame member variables and set it to FALSE in CMainFrame’s constructor and to TRUE in OnCreateClient. By checking it in OnSize we only adjust the splitters after they’ve been initialized: if ( m_initSplitters && nType != SIZE_MINIMIZED ) { // etc... OK! Pat yourself on the back and do something soothing to relax yourself (I like to pound my head on the keyboard - make sure you note where all the keys fly, you may need them). Take an Advil and then proceed... |