I solved my problem.
I had several problems inside of my OnPaint(), RecalcClientSize(), and RecalcLayout().
The problem was that I was storing the CDockContWnd's in a vector, as well as it's own CDockDialog children.
Inside of CDockContWnd::OnPaint() I was assuming that if it was in the vector it was a window. However I was only doing a delete in the vector and not actually removing it from the vector thus causing the pointer to become invalid, yet the vector size was unchanged. I fixed it by calling vector::clear() at the end of my delete cleanup loop. I also added several protection mechanisms in OnPaint() and any other layout/drawing function that required window or client rects.
Code:
if (m_vChildren[i] && IsWindow(m_vChildren[i]->m_hWnd))
{
//Proceed with whatever we need to do
}
I am re-designing this whole thing now to shutdown using destructors instead of destroy since I know how to fix the entire problem.
I also added a RecalcClientSize() function to CMainFrame. This takes into account all docked windows and resizes the client window based on what and where other windows are docked. Now each CDockContWnd has a dock state which tells me where it has been docked so I can compare coordinates like this:
Code:
void CMainFrame::RecalcClientSize(void)
{
CRect rect;
if (m_pView) m_pView->GetClientRect(&rect);
if (m_vDockWnds.empty()) return;
for (DWORD i=0;i<static_cast<DWORD>(m_vDockWnds.size());i++)
{
if (IsWindow(m_vDockWnds[i]->m_hWnd))
{
CRect rectDock;
m_vDockWnds[i]->GetWindowRect(&rectDock);
ScreenToClient(&rectDock);
//CString text;
//text.Format(L"Dock: %d %d %d %d\n"
// L"Client: %d %d %d %d",
// rectDock.left,rectDock.top,rectDock.right,rectDock.bottom,
// rect.left,rect.top,rect.right,rect.bottom);
//AfxMessageBox(text);
int mode=m_vDockWnds[i]->GetDockState();
//Window is on right side of client
if (rectDock.left<=rect.right && mode==DLG_DOCK_RIGHT) rect.right=rectDock.left;
//Window is on far left side of client - use right edge for left side of client
if (rectDock.right>=rect.left && mode==DLG_DOCK_LEFT) rect.left=rectDock.right;
//Window is on bottom of client - use top edge for bottom of client
//if (rectDock.top>=rect.bottom && rectDock.bottom>=rect.bottom) rect.bottom=rectDock.top;
//Window is on top of client - use bottom edge for top of client
//if (rectDock.top<=rect.top && rectDock.bottom>=rect.top) rect.top=rectDock.bottom;
}
}
//CString text;
//text.Format(L"%d %d %d %d",rect.left,rect.top,rect.right,rect.bottom);
//AfxMessageBox(text);
if (m_pView)
{
if (IsWindow(m_pView->m_hWnd))
{
CRect statusRect;
m_wndStatusBar.GetWindowRect(&statusRect);
ScreenToClient(&statusRect);
rect.bottom=statusRect.top;
m_pView->SetWindowPos(&wndTop,rect.left,rect.top,rect.Width(),rect.Height(),SWP_FRAMECHANGED);
}
}
}
As you can see it has a lot of debug messages in it which will be removed once I confirm all types of docking work correctly. I've also tested docking with a dock target - in other words docking more CDockDialog's inside of a CDockContWnd which calls CDockContWnd::RecalcLayout(). It works like a charm. Top and bottom docking has been disabled just so I can see if the left and right dock code works. Then I can use the left/right code as a template for the top/bottom code.
Code:
void CDockContWnd::RecalcLayout(void)
{
CRect dlgRect;
CRect clientRect;
GetClientRect(&clientRect);
CRect tempRect;
int iYPos=clientRect.top;
for (DWORD i=0;i<m_vChildren.size();i++)
{
m_vChildren[i]->GetWindowRect(&dlgRect);
tempRect.left=dlgRect.left;
tempRect.top=iYPos;
tempRect.bottom=tempRect.top+dlgRect.Height();
tempRect.right=tempRect.left+dlgRect.Width();
m_vChildren[i]->SetWindowPos(NULL,
tempRect.left,
tempRect.top,
tempRect.Width(),
tempRect.Height(),
SWP_NOZORDER | SWP_SHOWWINDOW);
//Send a WM_PAINT to the child window so it can redraw controls and any child windows
//it may have
m_vChildren[i]->Invalidate();
//Keep track of our current Y position in this container window
iYPos+=tempRect.Height()+1;
}
}
As you can see since I'm using a vector I can insert a window at any point. This means that as long as I provide some type of visual feedback and drag and drop tracking of windows, I can allow the user to drag any CDockDialog onto any CDockContWnd at any place. Based on coordinates then I can tell where in the vector I should insert the new window. This has a lot of flexibility and I can't wait to get it all working. Since the CRect class has a lot of functionality inside of it, testing where windows need to be placed will be quite simple.
I wish MFC would have provided better docking support, but no worries, I'm implementing my own. Perhaps I'll implement some custom UI elements next.