MFC: how to fix an MDI after replacing the CView with a CSplitterWnd having two CView-derived classes in the panes - mfc

I added a navigation sidebar in my CChildFrame by creating a CSplitterWnd in it and by adding a CScrollView and a CListView as splitter panes based on this answer. Is there a textbook way to do this? I'm asking because I ran into multiple side-effects like the document name no longer showing up in the applications titlebar or mousewheel no longer working for the CScrollView.
I guess, I need to redirect messages arriving in my CChildFrame somehow to make my CScrollView the default receiver for CView-related messages as the CSplitterWnd breaks the flow, because it's not derived from CView. Can someone point me in the right direction?
(Note: This is still done with VS 2008.)
Update: I just created a separate question for the application window title update issue: MFC: After applying a CSplitterWnd to my CChildFrame the main window title isn't updated any more

These code samples are not the most suitable ones, because they create CWnd-derived panes, not CView-derived ones. The views are not in some way associated to the document. You need to call the CDocument::AddView() method, to add every view you created to the document's list of views. The pContext parameter contains a pointer to the document, among other members.
Put the code below in your document class, to verify that your views have been created and associated to the document correctly:
POSITION pos = GetFirstViewPosition();
while (pos)
{
CView *pVw = GetNextView(pos);
AfxMessageBox(typeid(*pVw).name(), MB_OK | MB_ICONINFORMATION);
}
Also in each view class:
CDocument *pDoc = GetDocument();
if (pDoc) AfxMessageBox(pDoc->GetTitle(), MB_OK | MB_ICONINFORMATION);
else AfxMessageBox("The view has no associated Document!");
Note: The default implementation of the CChildFrame class calls CSplitterWnd::Create() rather than CSplitterWnd::CreateStatic() to create the splitter window, and if you move the splitter window to the end it destroys the panes that come off-view. Then if you move it back it creates them again, using the information in the document template or the existing views. You may need to override this behavior too, if you finally make a dynamic splitter window. Better check the MFC source of the version you are using.

There seem to be historical interferences between splitter and view concerning mouse wheel: https://forums.codeguru.com/showthread.php?42826-BUG-CSplitterWnd-Mouse-Wheel
I wont't mark this as solution, as I don't comprehend the backgrounds completely, but it's a workaround for the moment:
BOOL CMyScrollViewDerivedClassInTheStaticSplitter::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// CSplitterWnd prohibits mousewheel somehow. Doing it explicitely here:
CPoint pos = GetScrollPosition();
pos.y -= zDelta;
ScrollToPosition(pos);
return CScrollView::OnMouseWheel(nFlags, zDelta, pt);
}

Related

MFC: After applying a CSplitterWnd to my CChildFrame the main window title isn't updated any more

Still using VS 2008, I have split my MDI view class in my CChildFrame to facilitate a navigation sidebar (a CListCtrl) next to my old CScrollView using a static splitter (source code). This however implied two side effects: Beside the mouse wheel no longer working (where I found a workaround for), the application window is no longer updated on SetPathName(). What do I need to do to bridge the splitter so the framework updates the application window again based on my CDocument?
On your MainFrame class, just put a method
void CMyFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
__super::OnUpdateFrameTitle(bAddToTitle);
}
and a breakpoint there, to see what happens. If needed, you will have to provide your own override instead of calling the __super implementation.

MFC Custom Control Not Appearing On Dialog

Using Visual Studio 2013, I created a dialog resource using the resource editor. It is a child control with no border and is just a collection of radio buttons, push buttons, and static text. I want to turn this into a custom control in order to place this in several different locations. Let's call this a "Panel".
I then created a regular dialog and using the Toolbox "Custom Control", defined an area for the Panel. The Panel registers itself and has a valid window handle.
I used the following example:
https://www.codeproject.com/Articles/521/Creating-Custom-Controls
The parent's DDX gets hit and the _panel is properly instantiated:
MyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX)
DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)
}
I read that I need to override the OnPaint() and OnEraseBkgnd(CDC* pDC) methods so the Panel class has these but they are empty. I do not have any custom painting to do as the Panel contains nothing but regular buttons.
What do I have to include in OnPaint()?
I also noticed that none of the member buttons are instantiated in the Panel like would normally happen in a dialog's DoDataExchange method. Instead, I've had to resort to dynamically creating each of the control's inside the Panel's PreSubclassWindow() method:
void MyPanel:PreSubclassWindow()
{
_groupBox.Create(_T("Options"), WS_CHILD|WS_VISIBLE|BS_GROUPBOX, CRect(11, 11, 112, 231), this, IDC_STATIC_GROUPBOX);
//... do this for every dialog element??? seems like overkill...
CWnd::PreSubclassWindow()
}
Why do I need to do this when I've already defined/designed the Panel and each of its controls in the resource editor?
If I do not do this in the PreSubclassWindow method, nothing will appear on the dialog.
Any help is appreciated. Thanks.
The article says override OnPaint and OnEraseBkgnd if you want to change the functionality. It doesn't say you have to override always.
Just remove ON_WM_PAINT and ON_WM_ERASEBKGND, remove OnPaint and OnEraseBkgnd if you don't need them. Or call the base class implementations if you are not making any changes:
void MyPanel::OnPaint() { CWnd::OnPaint(); }
BOOL MyPanel::OnEraseBkgnd(CDC* pDC) { return CWnd::OnEraseBkgnd(pDC); }
This will show a blank control with nothing in it, unless you add a child window to _panel as you have done in MyPanel:PreSubclassWindow
You are adding _groupBox to _panel. And you are adding _panel to the MyDialog.
MyDialog::DoDataExchange(...){DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)} is needed to invoke SubclassWindow for _panel. That in turn calls _groupBox.Create.
If MyPanel::OnPaint and MyPanel::PreSubclassWindow are not doing anything MyPanel appears as a blank control.
... do this for every dialog element??? seems like overkill...
You can directly add _groupBox to the main dialog. But if you want to add specific controls within MyPanel then you have to do it manually.
You can also create a child dialog within a main dialog. For example that's how a tab control works.

MFC tabbed MDI : too many OnAfterTaskbarActivate()

(My English can be strange o_o)
I work with MFC tabbed MDI, No Document/View
I delete auto-generated View Class, and use CSplitterWnd and my own View Class
"my own View Class" has two child: edit control and my custom control
(edit: I created new tabbed MDI, No Document/View MFC Project and tested that project by spy++. and the following problem occur AGAIN!)
(edit: Use Document/View: AGAIN!)
While I write this program, I found a strange problem:
When I validate window by clicking Aero Peek Thumbnail, my custom control is drawn so many times (WM_PAINT is sent many times)
I figured out CMDIFrameWndEx::OnAfterTaskbarActivate function calls RedrawWindow in that WM_PAINT, and OnAfterTaskbarActivate is callen so many times (31 times, yah)
Could you tell me what makes this problem and how to solve?
I had the same issue as this. My Solution is to override the OnAfterTaskbarActivate
add this to your MessageMap
ON_REGISTERED_MESSAGE(AFX_WM_AFTER_TASKBAR_ACTIVATE, OnAfterTaskbarActivate)
the function is as follows. please note this is exactly the same as the original function without the redraw.
LRESULT CMainFrame::OnAfterTaskbarActivate(WPARAM, LPARAM lp)
{
HWND hwndMDIChild = (HWND)lp;
if (hwndMDIChild != NULL && ::IsWindow(hwndMDIChild))
{
::SetFocus(hwndMDIChild);
}
return 0;
}
I hope this helps. it has resolved my issues.

Preventing Mouse-wheel scrolling of controls

I am developing an application using the MFC library and I am currently trying to prevent the user accidentally changing one of the combo box controls when they are scrolling the mouse wheel.
I am looking for a solution without deriving a new class from the CComboBox class and preventing the mouse scrolling there.
My understanding of the system is that Windows passes the WM_MOUSEWHEEL message to the Combo box control which handles it (scrolling the combo box) and then this is propagated up the chain of parent controls (so them to my CFormView etc.), which means I cannot prevent the scrolling by capturing the event in my form view.
Does anyone have a solution to this problem? Thanks in advance!
You can always derive a control from CComboBox and trap the WM_MOUSEWHEEL message in the control itself. Then simply use your new derived combo box in your form view.
If you don't want to create a derived class (perhaps it's too big a change for your project), you can subclass the combo box and trap the WM_MOUSEWHEEL there.
Override the PreTranslateMessage handler in the main window class and look for WM_MOUSEWHEEL messages. Compare the pMsg->hwnd handle in PreTranslateMessage handler with the combobox handle, if found, filter the messages away.

Tab Order with CTabCtrl and child CFormViews

In my application I have a CFormView with a CTabCtrl, I also have 4 CFormViews that are children of the main CFormView and that are shown/hidden when the user changes the selected tab.
However, I can't find a way to make the Tab Order to work properly. If the CTabCtrl has the focus, pressing the Tab key has no effect and if one of the child CFormView has the focus the Tab key will move the focus only around the controls inside the CFormView.
I tried changing the z-order of the visible child CFormView to be right after the CTabCtrl with SetWindowPos, changed the child CFormViews styles to WS_EX_CONTROLPARENT but nothing seems to work.
You've started out from the wrong implementation: you shouldn't make a CFormView with a CTabCtrl and then stuff more CFormViews into it. This isn't going to work right. Instead, you should work with CPropertySheet and CPropertyPage, where focus handling has already been taken care of. You will still be able to access the CTabCtrl owned by the CPropertySheet by calling GetTabControl(), but MFC will take care of the problems you've encountered.
Briefly: derive classes from CPropertySheet for each of the dialog windows you want to show (e.g., CConfigPage1, CConfigPage2). Create a Dialog resource in the Resource Editor for each of them, and do all of the other standard CDialog setup.
Next, derive a class from CPropertySheet (e.g., CProps), and (optionally) handle WM_SIZE and TCN_SELCHANGE.
Finally, derive a class from a CView descendent, like CScrollView (e.g., CViewMyAwesomeStuff). Then add member variables for the CPropertySheet and CPropertyPages, and handle WM_CREATE where you Add() each page to the property sheet and then Create(this,WS_CHILD|WS_VISIBLE) the property sheet.
Bonus: You can forward the CView::OnUpdate to each child CPropertyPage by calling GetPage() in a loop and calling a function on each of them, or you can send a message to each of them (use a user-defined message, like WM_APP+1). They can discover their parent's CDocument by calling GetParent()->GetParent()->GetDocument().