|
I tried a little experiment and compiled this docking framework under a Unicode build (making a new configuration, and using _UNICODE instead of _MBCS), and there was one place that I found that needs a small update:
sstate.h (122):
CMainState(const std::string& strKeyName):m_strMainKey(strKeyName)
should be
CMainState(const tstring& strKeyName):m_strMainKey(strKeyName)
(tstring instead of string)
-Daniel
|
|
|
|
|
Is tstring a STL-type or a Microsoft type?
I imagine that the tstring is something like typedef basic_string<TCHAR> tstring?
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.
|
|
|
|
|
I changed my copy of this code to use CString where possible (as I use CString all over the place so I couldn't see the point in dragging in ANOTHER string template class!). In fact, a totally STL-free version may go down well with some people - I know a lot of developers that are, for want of a better word, scared of the STL. Others may worry that MS supplies a duff implementation, etc. In fact, if you are using ATL7/VS.NET, you get a lot of new templates for arrays, lists, etc. etc. that may plug into this framework nicely.
Just my 2p.
Faith. Believing in something you *know* isn't true.
|
|
|
|
|
Stuffing every bit of the framework's code into header files really
makes it compile slow. Is their any workaround to reduce compile time?
|
|
|
|
|
Include it from your Precompiled header ( stdafx.h )
|
|
|
|
|
Sergey,
I have a couple of suggestions for you, as I am sure you are beavering away on a new WTL7 version of your excellent docuking window code ... (even though the code seems to be working fine under WTL7).
1. Double-clicking the caption bar of a docked window should float it.
2. Double-clicking the caption bar of a floating window should dock it. Currently this will maximise the window to full screen, which isn't desirable.
3. How about minimize/maximize buttons when the windows are docked? These could expand/contract the docked window to fill the docking area, sizing other windows so just their captions are visible.
Just some ideas.
|
|
|
|
|
Instead of having user-defined messages based off of WM_USER or WM_APP, I think its a better practice to use RegisterWindowMessage to guarantee yourself a unique message ID.
Just make up a GUID from GUIDGen, and have something like:
#define UWM_DOCK_MSG _T("UWM_DOCK_MSG-5DAD28E1-C961-11d5-8BDA-00500477589F")
static UINT UWM_DOCK = 0;
And then in the constructor of a class that sends or receives the message in question (a class that will be constructed before you need the message), have:
if(UWM_DOCK == 0)
{
UWM_DOCK = ::RegisterWindowMessage(UWM_DOCK_MSG);
}
(Note: you could do the call when you declare it:
static UINT UWM_DOCK = ::RegisterWindowMessage(UWM_DOCK_MSG);
but that doesn't work in release mode all the time)
You can handle these messages in your message map just like any other, such as:
MESSAGE_HANDLER(UWM_DOCK, OnDock)
Thanks,
-Daniel
|
|
|
|
|
Thank you Daniel for suggestion,
of course when I wrote this code I had such idea,
but how about this???
====Microsoft Platform SDK Documentation, RegisterWindowMessage : ========================
Remark:
...
Only use RegisterWindowMessage when more than one application must process the same message. For sending private messages within a window class, an application can use any integer in the range WM_USER through 0x7FFF. (Messages in this range are private to a window class, not to an application. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use values in this range.)
...
=========================================================
If I miss something I'll be very appreciated for any suggestion.
Sergey Klimov
|
|
|
|
|
I realized I left out an important part in my first message . I should have started out "when you're doing base templatized windowing classes ...". I do agree that WM_USER + x is fine as long as you're aware of all the possible messages for a window before hand. When you're making a templatized base class that helps implement a window (ala WTL or ATL), its not always the case that you know all the messages that the window expects (unless no one but you uses the class of course )
This is really just my opinion from my own experience, but when you have a base templatized class for helping implement a window - either to be used as a pure base class, or as one of several "mix-in" classes, there have been occasions when you can unknowingly have conflicting message IDs if you base them off of WM_USER if you're not careful.
Example 1
Let's say that somebody else wants to have a window based off of your dockwins::CTitleDockingWindowImpl<> for a docking pane window, but they also want some other functionality. Let's say this other functionality is wrapped up in a separate Impl style classs, and takes a template parameter for the base (which they give as dockwins::CTitleDockingWindowImpl<>) and the message map is chained to the base.
Well, CTitleDockingWindowImpl derives from CDockingWindowBaseImpl, which uses the message WMDF_NDOCKSTATECHANGED - that really is WM_USER + 1. But let's say this other "Impl" class that uses CTitleDockingWindowImpl also uses a custom WM_USER + 1 message. There's a conflict, and in this case, if the "Impl" style class doesn't say "bHandled = FALSE;" when handling WM_USER+1, then CDockingWindowBaseImpl will never see the message.
Now, whoever it was using both CTitleDockingWindowImpl and the other "Impl" class could have caught this problem by tracing through all the class hierarchy, and seeing what messages are being used. This is what I personally do, but it's easy to let it slip when you're just using someone elses cool templatized windowing class, or you run into a problem (like "hey, the window doesn't dock").
Example 2
Another example is that you're writing a quick Impl style class that is meant to super/subclass a common control (i.e., list view) or window control (i.e., combo box). Well, these guys have special message based off of WM_USER already. See MSDN on WM_APP, and note that the "extended" common control message (i.e., LVM_SETITEM), are in the WM_USER range. If you were to introduce a special message based off of WM_USER that conflicted with one of these, you might introduce a subtle bug that doesn't get caught for a while. Now in this particular case, you can base your special message off of WM_APP and be OK.
Example 3
One last example of why WM_USER based messages are problematic is because of what I've see sometimes when reflecting notifications or commands, and using someone elses class that might not do DEFAULT_REFLECTION_HANDLER(). In ATL/WTL, when notifications are reflected, you are essentially having the parent window send back certain window messages "translated" to the WM_USER range - specifically, the "original" message ID is added to OCM__BASE (which is WM_USER + 0x1c00). Well, if by some chance the window receiving this message is expecting its own private message to have one of these message IDs, bad things can happen (try reflecting notifications to a generic tree view, and you'll see what I mean).
The reason I suggest RegisterWindowMessage is that it helps prevent such scenarios for the user of your templates when you're distributing them to others. It helps "encapsulate" your functionality just a little more.
You can take or leave the suggestion , but there it is.
-Daniel
|
|
|
|
|
1) Annoying:
Movement of the window should happen only after WM_LBUTTONUP is sent. WM_CAPTURECHANGED should abort dragging operation.
Example? Here: start dragging any docking window, now press [Alt]+[Tab] to switch to another application - docking window will be moved.
2) Rare, but still annoying:
While dragging any docking window with your mouse, press [Alt] (Menu will be highlighted), then press [Down Arrow] (to show submenu). Application will enter infinite loop.
For added effect click Right Mouse Button on application's tab in Taskbar to bring up System Menu.
OS: WinXP
|
|
|
|
|
Thank you Maxim!
here is a quick solution:
please, replace TrackDragAndDrop function in DDTracker.h,57
=====================begin paste=========================
template<class T>
bool TrackDragAndDrop(T& tracker,HWND hWnd)
{
bool bResult=true;
tracker.BeginDrag();
::SetCapture(hWnd);
MSG msg;
while((::GetCapture()==hWnd)&&
(GetMessage(&msg, NULL, 0, 0)))
{
if(!tracker.ProcessWindowMessage(&msg))
{
switch(msg.message)
{
case WM_MOUSEMOVE:
tracker.OnMove(GET_X_LPARAM( msg.lParam), GET_Y_LPARAM(msg.lParam));
break;
case WM_RBUTTONUP:
::ReleaseCapture();
tracker.OnDropRightButton(GET_X_LPARAM( msg.lParam), GET_Y_LPARAM(msg.lParam));
break;
case WM_LBUTTONUP:
::ReleaseCapture();
tracker.OnDropLeftButton(GET_X_LPARAM( msg.lParam), GET_Y_LPARAM(msg.lParam));
break;
case WM_KEYDOWN:
if(msg.wParam!=VK_ESCAPE)
break;
case WM_SYSKEYDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONDOWN:
::ReleaseCapture();
tracker.OnCancelDrag(GET_X_LPARAM( msg.lParam), GET_Y_LPARAM(msg.lParam));
bResult=false;
default:
DispatchMessage(&msg);
}
}
}
tracker.EndDrag(!bResult);
assert(::GetCapture()!=hWnd);
return bResult;
}
=====================end paste===========================
Thank you again, as I've heard new WTL version will be released soon, then I'll post fixed version of docking window
Sergey Klimov
|
|
|
|
|
If you change the positioning order of toolbars on a rebar, the new positions are not updated correctly when restarting the application, which causes all sorts of problems. To fix it, change the CRebarStateAdapter::Restore function in sstate.h:
Change:
if((::RegQueryValueEx(key,sstrKey.str().c_str(),NULL,&dwType,
reinterpret_cast<lpbyte>(&rbi),&cbData)==ERROR_SUCCESS)
&&(dwType==REG_BINARY))
m_rebar.SetBandInfo(i, &rbi);
To:
if((::RegQueryValueEx(key,sstrKey.str().c_str(),NULL,&dwType,
reinterpret_cast<lpbyte>(&rbi),&cbData)==ERROR_SUCCESS)
&&(dwType==REG_BINARY))
{
m_rebar.MoveBand(m_rebar.IdToIndex(rbi.wID), i);
m_rebar.SetBandInfo(i, &rbi);
}
Another bug occurs when compiling with _WIN32_IE >= 0x0400 - the rebars are restored offset to the left, and each time you restart the application, they shift over some more. The culprit is the following from CRebarStateAdapter::Store:
#if (_WIN32_IE >= 0x0400)
| RBBIM_HEADERSIZE | RBBIM_IDEALSIZE
#endif
If you remove this, it all works OK.
Great code BTW. I hope to never use MFC for new projects again!
|
|
|
|
|
Thank you Robert very much for your valuable postings!!!
|
|
|
|
|
the latter of these two bugs is still not fixed in the current version (4th July).
This fix still needs to be applied.
|
|
|
|
|
The latter of these two bugs is still not fixed, additionally if you use the CTabbedMDICommandBarCtrl you will have to edit "tabbedmdi.h", goto line 1316 and remove the "RBBIM_IDEALSIZE" flag.
e.g.
...
for(int i = 0; i < nCount; i++)
{
#if (_WIN32_IE >= 0x0500)
// REBARBANDINFO rbi = { sizeof(REBARBANDINFO), RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_STYLE };
REBARBANDINFO rbi = { sizeof(REBARBANDINFO), RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_STYLE };
::SendMessage(GetParent(), RB_GETBANDINFO, i, (LPARAM)&rbi);
if(rbi.hwndChild == m_hWnd)
...
|
|
|
|
|
The first "correction" was typed in incorrectly (parenthesis instead of angle brackets).
The second correction seems to be correct.
|
|
|
|
|
I have been using this code to create docking windows that contain regular Windows dialog controls. These controls would not always operate correctly due to the fact that IsDialogMessage isn't being called when appropriate. In order to make them work (normal edit controls can highlight the problem), then you need to add a call to IsDialogMessage to your mainframe PreTranslateMessage function (but only if your docking window has focus).
For example, if you have a single docking window (m_wndProperty) that contains dialog controls, you would have something like:
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
// Who has the focus?
HWND hWndFocus = GetFocus();
// If the property window has focus (or one of its children)
// then see if this is a dialog message
if (hWndFocus == m_wndProperty.m_hWnd || m_wndProperty.IsChild(hWndFocus))
{
// Process dialog notifications
if (IsDialogMessage(pMsg))
return TRUE;
// Fall through
}
// Pass the messages to the base class?
if (CDockingFrameImpl<cmainframe>::PreTranslateMessage(pMsg))
return TRUE;
// Finally, let the view have the message
return m_view.PreTranslateMessage(pMsg);
}
This works fine for me and it also means that normal accelerator keys are handled correctly.
|
|
|
|
|
When restoring a window using CWindowStateMgr, the main window is not activated when the application is run, so the application doesn't have keyboard focus. I see this on XP with the MDISample.exe. Note that the SetForegroundWindow call in the apps Run function doesn't make any difference.
Any ideas?
|
|
|
|
|
Just add SetFocus() in OnInitialize() method of the CMainFrame class,like this:
LRESULT OnInitialize(...)
{
m_stateMgr.Restore();
UpdateLayout();
>>>>>>>>
SetFocus();
return 0;
}
|
|
|
|
|
Your work is simply great.
I am strongly considering to use WTL now.
But, you know, we always desire something more...
Can you add a 'Tabbed Window functionality' like in Visual Studio .Net, maybe using the infos given in "A simple tabbed MDI for WTL" at http://www.codeproject.com/useritems/wtlmditab.asp
I think it is safer if you do it, rather than myself.
Thank you,
Marcello
|
|
|
|
|
I've successfully been able to integrate my "TabbedMDI" and "TabbedFrame" classes with this docking window framework. The look is closer to the tabs in Visual Studio.Net than http://www.codeproject.com/useritems/wtlmditab.asp has. With it, you can have a tabbed MDI, as well as have any of the docking windows use tabs to switch between child "view" windows. Eventually, I'd like to do a CodeProject article for it, but for now you can get the latest bits from the wtl group on groups.yahoo.com or viksoe.dk. You might want to also check out the CoolTabCtrls that it is based on.
One other thing that's nice with the tabbed MDI, is that you don't need the fix to the "MDI command bar bug", because you can do everything you need with the tabbed MDI!
The file download is "TabDemo.zip", and it comes with 2 samples that show the TabbedMDI and TabbedFrame used in a plain vanilla WTL app. I've been able to integrate it with *this* docking framework as well by doing the following (for the MDISample from WTLDockingWindows_demo):
Includes
- Copy the following files from the TabDemo.zip "include" directory to the WTLDockingWindows_demo.zip "include" directory:
- atlgdix.h
- CoolTabCtrls.h
- TabbedFrame.h
- TabbedMDI.h
- In MDISample.cpp, after #include "resource.h", add the lines:
#include "atlgdix.h"
#include "CoolTabCtrls.h"
#include "TabbedFrame.h"
#include "TabbedMDI.h"
- Add the line
#include <atlmisc.h>
either in <stdafx.h>, or in MDISample.cpp before <atlframe.h>.
Tabbed MDI
- In mainfrm.h add a member variable
CTabbedMDIClient m_tabbedClient;
- In CMainFrame::OnCreate, after the line "CreateMDIClient();", add the line
m_tabbedClient.SubclassWindow(m_hWndMDIClient);
- In ChildFrm.h, find all 3 occurences of
CMDIChildWindowImpl and replace them with
CTabbedMDIChildWindowImpl
Tabbed Frame (an example of turning the "project" docking window into a tabbed view with 1 or more child "view" windows)
- In FoldersDockingWindow.h, instead of inheriting from
dockwins::CStateKeeper<
dockwins::CTitleDockingWindowImpl<
CFoldersDockingWindow,
CWindow,
dockwins::COutlookLikeTitleDockingWindowTraits
>
>
inherit instead from
CTabbedFrameImpl<
CFoldersDockingWindow,
CDotNetTabCtrl,
dockwins::CStateKeeper<
dockwins::CTitleDockingWindowImpl<
CFoldersDockingWindow,
CWindow,
dockwins::COutlookLikeTitleDockingWindowTraits
>
>
>
also, change the typedef of baseClass to the same
- Add the following method to CFoldersDockingWindow:
void UpdateBarsPosition(RECT& rect, BOOL bResizeBars = TRUE)
{
}
- Change the implementation of CFoldersDockingWindow::OnSize from
if(wParam != SIZE_MINIMIZED )
{
RECT rc;
GetClientRect(&rc);
::SetWindowPos(m_tree.m_hWnd,NULL,
rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, SWP_NOZORDER | SWP_NOACTIVATE);
}
bHandled = FALSE;
return 1;
to
if(wParam != SIZE_MINIMIZED)
{
baseClass::UpdateLayout();
}
bHandled = FALSE;
return 1;
- In CFoldersDockingWindow::OnCreate, after the "FillTree(m_tree)" line, add:
SetTabAreaOnTop(false);
CreateTabWindow(m_hWnd, rcDefault);
this->AddTab(m_tree, _T("Project"), MAKEINTRESOURCE(IDR_MAINFRAME));
- You'll notice that the tree view's colors are weird (the color behind the "+" and "-" buttons). The tabs require notifications to be reflected from the parent (primarily for custom drawing). The tree view and tabs are siblings, so they both get their notifications reflected back. If you use the out of the box "CTreeViewCtrl", these reflected notifications aren't dealt with properly by the tree view. One thing you can do to solve the problem is to derive your own view based on CTreeViewCtrl
class CProjectView : public CWindowImpl<CProjectView, CTreeViewCtrlEx>
{
public:
DECLARE_WND_SUPERCLASS(
_T("ProjectTreeView"),CTreeViewCtrlEx::GetWndClassName())
...
and add to the end of the message map:
DEFAULT_REFLECTION_HANDLER()
I probably need to look into fixing this, so you don't have to do this.
- If you'd like to see another tab in the window, try this:
- Add to CFoldersDockingWindow the member variable:
CEdit m_edit;
- In CFoldersDockingWindow::OnCreate, after the "AddTab" line, add the lines
m_edit.Create(m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL |
ES_AUTOHSCROLL | ES_AUTOVSCROLL |
ES_MULTILINE | ES_NOHIDESEL);
this->AddTab(m_edit, _T("Edit"), IDI_WARNING, NULL);
If you run into to problems or questions, let me know.
-Daniel Bowen
|
|
|
|
|
Daniel,
I have an example SDI project that uses your sexy DotNet tabs (quite easy) and have also integrated this into the SDI docking frame code (more difficult). I will post details of how to use your tabs in an SDI app and how to combine a tabbed SDI app with these docking window classes soon...
Regards.
|
|
|
|
|
|
any chance of you sharing your updated source code ?
|
|
|
|
|
I have noticed a small bug that shows up the in the SDI Sample. This bug happens when the SDI sample is run for the first time. The docking window that is initially floating (m_sampleDockWnd) is not displayed. This is because it is created invisible and because there is no information about the docking window in the registry for the docking window, the window is never shown.
I am uncertain if this is the correct fix, but I fixed the problem by making the Restore member function in the CWindowStateMgr class return a bool (sstate.h, line 401) that indicates whether the registry restore was successful or not, the revised code for the Restore function is shown below:
<br />
bool Restore()<br />
{<br />
CMainState mstate(m_strMainKey);<br />
CRegKey key;<br />
if (mstate.Restore() &&<br />
(key.Open(mstate.MainKey(),<br />
ctxtMainWindow,<br />
KEY_READ) == ERROR_SUCCESS))<br />
{<br />
m_pImpl->Restore(&mstate,key);<br />
return true;<br />
}<br />
else<br />
m_pImpl->RestoreDefault();<br />
return false;<br />
}<br />
Then in the call to restore SDISample, in mainfrm.h, line 167 I changed the code to test the return code from the Restore function and show the docking window if the restore has failed. Once the docking window has been shown in its floating state its position will be correctly saved and restored.
<br />
if (!m_stateMgr.Restore())<br />
m_sampleDockWnd.ShowWindow(SW_SHOW);<br />
|
|
|
|
|