MFC - Printing - Changing page orientation from a custom pagesetup dialog - c++

I am developing a custom print dialog and page setup using MFC and VS2008 for my Win32 program. Since the code is legacy, I can't take much advantage from MFC view/doc architecture. As a result, I wrote a printing code completely from scratch.
I setup CPrintInfo, instantiate my custom print dialog box and hook this dialog box to the CPrintInfo I just created. When my custom print dialog is up, I have a radio button to let a user toggles the page orientation. For some reasons, I couldn't modify the current DEVMODE at the run-time. As a result, every page I print will end up as a portrait.
Even if I manually set pDevMode->dmOrientation to DMORIENT_LANDSCAPE from the event handler of the custom print dialog, the printing result is still ended up as portrait. I am really not sure why this is happening and how to modify the DevMode after the print dialog is up.
Thank you in advance for any help.
Here is the code I have:
void PrintSomething(CWnd* currentWnd) {
// Create CPrintInfo
CPrintInfo* pPrintInfo = new CPrintInfo;
SetupPrintInfo(pPrintInfo); // simply setup some member variables of CPrintInfo
// Create a custom print dialog
CustomPrintDlg* pCustomPrtDlg = new CustomPrintDlg(FALSE, PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS
| PD_HIDEPRINTTOFILE | PD_NOSELECTION, pPrintInfo, currentWnd);
SetupPrintDialog(pPrintInfo,pCustomPrtDlg);
if ( AfxGetApp()->DoPrintDialog(pCustomPrtDlg) == IDOK ) {
... // proceed a print loop
}
}
Code for setting up the custom print dialog:
void SetupPrintDialog(CPrintInfo* pPrintInfo,CustomPrintDlg* pCustomPrtDlg) {
delete pInfo->m_pPD;
pInfo->m_pPD = pCustomPrtDlg;
pInfo->m_pPD->m_pd.hInstance = AfxGetInstanceHandle();
pInfo->m_pPD->m_pd.lpPrintTemplateName = MAKEINTRESOURCE(IDD_CUSTOM_PRTDLG);
// Set the Flags of the PRINTDLG structure as shown, else the
// changes will have no effect.
pInfo>m_pPD->m_pd.Flags |= PD_ENABLEPRINTTEMPLATE;
// Set the page range.
pInfo>m_pPD->m_pd.nMinPage = 1; // one based page numbers.
pInfo>m_pPD->m_pd.nMaxPage = 0xffff; // how many pages is unknown.
}
When a user toggles the radio button to Landscape, this function will be invoked:
void CustomPrintDlg::OnLandscapeChecked() {
// set the current Devmode to landscape
LPDEVMODE pDevMode = GetDevMode();
GlobalUnlock(pDevMode);
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
}
A pseucode for the custom print dialog class:
class CustomPrintDlg: public CPrintDialog {
... // just override some methods from CPrintDialog
};
Thanks again,
Unagi

I figured out the solution:
All I need is to call GlobalLock to obtain a pointer to the Devmode before changing the current DevMode.
void CustomPrintDlg::OnLandscapeChecked()
{
// set the current Devmode to landscape
LPDEVMODE pDevMode = GetDevMode();
GlobalLock(pDevMode);
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
GlobalUnlock(pDevMode)
}
Thanks again for helping me.

Nowhere in your example code do you show how you're creating the DC for printing. When you call CreateDC, you must pass a pointer to a DEVMODE structure; this defines whether the printing will be portrait or landscape.

Related

Closing Popup and setting button label

I'm writing a C++ wxWidgets calculator application. I want to compress trigonometric function buttons into a single one to save on space, using what's basically a split button. If you left click on it, the current option is used. If you right click, a popup menu is opened, which contains all the buttons; when you click on one of them, it is used and the big button changes.
I've been suggested to use wxComboBox and other stuff for this job, but I preferred using wxPopupTransientWindow because this way I can display the buttons in a grid, making everything - in my opinion - neater.
Problem is, when I choose an option from the menu, the main button's ID changes (because when I reopen the menu the previously clicked button is light up as its ID and the big button's ID match), but the label does not. Furthermore, the popup is supposed to close itself when you left click on one of the buttons, but it does not.
This is the code for the custom button in the popup which is supposed to do all that stuff:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetParent()->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
this->GetParent()->Close(true);
}
}
This is the code for the custom button in the main frame which opens up the popup (temporary setup just to test if the whole thing is working):
void ikeButton::rightClick(wxMouseEvent& evt) // CREA PANNELLO ESTENSIONE
{
if (flags & EXPANDABLE)
{
std::vector<expandMenuInfo> buttons;
buttons.push_back(expandMenuInfo(L"sin", 3001));
buttons.push_back(expandMenuInfo(L"cos", 3002));
buttons.push_back(expandMenuInfo(L"tan", 3003));
buttons.push_back(expandMenuInfo(L"arcsin", 3004));
buttons.push_back(expandMenuInfo(L"arccos", 3005));
buttons.push_back(expandMenuInfo(L"arctan", 3006));
wxPoint p = this->GetScreenPosition();
size_t sz = this->GetSize().GetHeight() / 1.15;
expandMenu* menu = new expandMenu(this, buttons, sz, wxPoint(
p.x, p.y + this->GetSize().GetHeight() + 2));
menu->SetPosition(wxPoint(
menu->GetPosition().x - ((menu->GetSize().GetWidth() - this->GetSize().GetWidth()) / 2),
menu->GetPosition().y));
menu->Popup();
}
}
Let me know if I need to show more code.
This is probably a terrible way of doing this, but this is basically the first "serious" application I'm creating using the wxWidgets framework. Any help is appreciated.
when I choose an option from the menu, the main button's ID changes
(because when I reopen the menu the previously clicked button is light
up as its ID and the big button's ID match), but the label does not.
If you're creating the popup menu like in your previous post, you had a popup window with a panel as its child and the buttons were then children of the panel layed out with a sizer.
If that's still the case, this->GetParent() and should be the panel, this->GetParent()->GetParent() should be the popup. So this->GetParent()->GetParent()->GetParent() should be the trig function button (assuming you created the popup with the trig function button as the parent).
So I think the line wxWindow* mBtn = this->GetParent()->GetParent(); should be changed to wxWindow* mBtn = this->GetParent()->GetParent()->GetParent();.
Or slightly shorter wxWindow* mBtn = this->GetGrandParent()->GetParent();;
the popup is supposed to close itself when you left click on one of
the buttons, but it does not.
It looks like wxPopupTransientWindow has a special Dismiss method that is supposed to be used to close it.
So I think the line this->GetParent()->Close(true); should be changed to this->GetGrandParent()->Dismiss(); (Assuming as above that the buttons in the popup are children pf a panel).
Alternately, if you want a solution that will work regardless of the parentage of the controls in the popup window, you could have a utility function to find the popup ancestor which would look something like this:
wxPopupTransientWindow* FindPopup(wxWindow* w)
{
wxPopupTransientWindow* popup = NULL;
while ( w != NULL )
{
popup = wxDynamicCast(w, wxPopupTransientWindow);
if ( popup )
{
break;
}
w = w->GetParent();
}
return popup;
}
This uses the wxDynamicCast function which is slightly different from the c++ dynamic_cast expression. wxDynamicCast uses wxWidgets' RTTI system to check if the given pointer can be converted to the given type.
Then the mouseReleased method could use this utility function something like this:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxPopupTransientWindow* popup = FindPopup(this);
if (popup ) {
wxWindow* mBtn = popup->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
mBtn->Refresh();
popup->Dismiss();
}
}
}
I'm not sure why you're setting trig function button to have a new id, but I assume you have a reason.
To make the SetLabel method work in your custom button class, I think the easist thing is to call the SetLabel() method in the button's constructor. This will store the string passed to the constructor in the button's internal label member.
Based on other questions, I think the ikeButton constructor looks something like this:
ikeButton::ikeButton(wxFrame* parent, wxWindowID id, wxString text,...
{
...
this->text = text;
To store the label, you would need to change the line this->text = text; to
SetLabel(text);
And when you draw the button, I think the method you use looks like this:
void ikeButton::render(wxDC& dc)
{
...
dc.DrawText(text, ...);
}
You would need to change, the line dc.DrawText(text, ...); to
dc.DrawText(GetLabel(), ...);
Likewise, any other references to the button's text member should be changed to GetLabel() instead.
Finally, when you set the label in the expandButton::mouseReleased method, it might be a good idea to call button's Refresh() method to force the button to redraw itself. I added that my suggestion for the mouseReleased method above.

MFC MDI CtabView hidden / remove navigation controls that are not used

I have 4 buttons that have no direct application to my program. I would like to hide or remove them. I've searched to find out what they are called...they look like navigation, left/right arrows, next/previous. I can't seem to find what they are called. I have looked on the Microsoft site and looking at the CTabView members doesn't seem to jump out and say "hey, these are what you're looking for"....
I'm hoping it is a relatively easy task. Anyone know how to just "turn them off"?
Thanks.
UPDATE:
I was able to resolve the C4430 mentioned by moving the new function before the existing in OutputWnd.h and that solved the issue in the output pane area where the arrows are now gone. What I forgot to mention is that I have another mechanism AddView that is creating 2 runtime classes and putting those into a tabbed document view. This time, I've been using GetTabControl() to make it pretty, but this also suffers from the same issue, the scroll arrows are now present.
This is what the code looks like that creates the 2 new tabs:
Above this code is the constructor/destructor/headers, nothing else.
IMPLEMENT_DYNCREATE(CTrackView, CTabView)
void CTrackView::OnInitialUpdate()
{
// add document views
AddView(RUNTIME_CLASS(CTrainView), AfxStringID(IDS_TRAIN));
AddView(RUNTIME_CLASS(CStationView), AfxStringID(IDS_STATION));
GetTabControl().EnableTabSwap(TRUE);
GetTabControl().SetLocation(CMFCBaseTabCtrl::Location::LOCATION_BOTTOM);
GetTabControl().ModifyTabStyle(CMFCTabCtrl::STYLE_3D);
GetTabControl().SetActiveTabBoldFont(TRUE);
CTabView::OnInitialUpdate();
}
I have tried to comment out CTabView::OnInitialUpdate(); but I know it won't and didn't affect the arrows. I done some searching on the control and I don't see any examples to remove the arrows, I assume another override is in order. I will follow the method shown in the other example, I am looking to see if it can be fixed similar to what has been shown.
Do these share the same CMFCTabCtrl mechanism or is this something different?
Update 2:
The OutputWnd.cpp that the MFC wizard creates has the same issue now that I've modified the tab control, I can't find the right pointer to fix the issue where &tabCtrl()is an unknown identifier. EDIT: This was the issue, I found the fix, see update 3 to see the resolution.
int COutputWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDockablePane::OnCreate(lpCreateStruct) == -1)
return -1;
CRect rectDummy;
rectDummy.SetRectEmpty();
// Create User Define tab style:
int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
// If the key doesn't exist, UserTableStyle will be 0 or FALSE;
if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type
int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
if (!m_wndTabs.Create(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle), rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
else { // Default tabs style if Reg key does not exist i.e. new install/program reset
if (!m_wndTabs.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
// Nicely hack to access protected member
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
void SetDisableScroll() { m_bScroll = FALSE; }
};
// One-Liner to Disable navigation control
((CMFCTabCtrlEx*)&tabCtrl())->SetDisableScroll();
// Create output panes:
const DWORD dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER;
if (!m_wndOutputBuild.Create(dwStyle, rectDummy, &m_wndTabs, 2) ||
!m_wndOutputDebug.Create(dwStyle, rectDummy, &m_wndTabs, 3))
{
TRACE0("Failed to create output windows\n");
return -1; // fail to create
}
UpdateFonts();
CString strTabName;
BOOL bNameValid;
//Attach list windows to tab:
bNameValid = strTabName.LoadString(IDS_STATUS_TAB);
ASSERT(bNameValid);
m_wndTabs.AddTab(&m_wndOutputBuild, strTabName, (UINT)0);
bNameValid = strTabName.LoadString(IDS_DEBUG_TAB);
ASSERT(bNameValid);
m_wndTabs.AddTab(&m_wndOutputDebug, strTabName, (UINT)1);
int EnableDebugTab = AfxGetApp()->GetProfileInt(_T("Settings"), _T("EnableDebugTab"), 0); //Get value from registry
if (EnableDebugTab == FALSE)
{
OnOutputtabsDebug(); //Check to see if it should be enabled
}
return 0;
}
Update 3:
I found the answer to my unknown identifier, I was trying to call a function that didn't exist. Reformed as such and it works as expected:
// Nicely hack to access protected member
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
void SetDisableScroll() { m_bScroll = FALSE; }
};
// One-Liner to Disable navigation control
((CMFCTabCtrlEx*)&m_wndTabs)->SetDisableScroll();
There is no public, documented API to hide those navigation buttons. However, digging into the MFC sources finds that:
the arrows are CMFCTabButton m_btnScrollLeft, m_btnScrollRight, m_btnScrollFirst, m_btnScrollLast; in CMFCTabCtrl;
they are created in CMFCTabCtrl::OnCreate whenever CMFCTabCtrl::m_bScroll is TRUE;
CMFCTabCtrl::m_bScroll is set to TRUE in CMFCTabCtrl::Create if any of the styles STYLE_FLAT, STYLE_FLAT_SHARED_HORZ_SCROLL, STYLE_3D_SCROLLED, STYLE_3D_ONENOTE, STYLE_3D_VS2005, or STYLE_3D_ROUNDED_SCROLL is used.
With that insight, the navigation buttons can be turned off in a couple of ways.
By the book: create the CMFCTabCtrl without any of the styles that enable those buttons, which leaves only STYLE_3D available. That loses the navigation buttons, indeed, but the styling of the tab control also becomes different, and the tabs themselves display as small rectangles rather than trapezoids.
Undocumented: override the CMFCTabCtrl::m_bScroll and set it to FALSE before the tab window gets created. For example, derive a class CMyTabCtrl to clear m_bScroll.
class CMyTabCtrl : public CMFCTabCtrl
{
protected:
BOOL PreCreateWindow(CREATESTRUCT &cs) override
{
m_bScroll = FALSE;
return CMFCTabCtrl::PreCreateWindow(cs);
}
};
Then instantiate the control as CMyTabCtrl m_wndTabs; instead of CMFCTabCtrl m_wndTabs;.
I remembered I solved this in the past with an One-liner.
Here a solution (hack) to direct access the m_bScroll Variable in your CTrackView::::OnCreate() function. OnInitialUpdate() comes too late as dxiv explained.
int void CTrackView::::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CTabView::OnCreate(lpCreateStruct) == -1)
return -1;
// add document views
AddView(RUNTIME_CLASS(CTrainView), AfxStringID(IDS_TRAIN));
AddView(RUNTIME_CLASS(CStationView), AfxStringID(IDS_STATION));
:
:
// Nicely hack to access protected member
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
void SetDisableScroll() { m_bScroll = FALSE; }
};
// One-Liner to Disable navigation control
((CMFCTabCtrlEx*)&GetTabControl())->SetDisableScroll();
}

How does MFC's "Update Command UI" system work?

I'd like to know more about how this system works, specifically when and how the framework actually decides to update a UI element.
My application has a 'tools' system where a single tool can be active at a time. I used the "ON_UPDATE_COMMAND_UI" message to 'check' the tool's icon/button in the UI, which affected both the application menu and the toolbars. Anyway, this was all working great until some point in the last couple of days, when the toolbar icons stopped getting highlighted properly.
I investigated a little and found that the update command was only being received when the icon was actually clicked. What's strange is this is only affecting the toolbars, not the menu, which is still working fine. Even when the buttons in the menu are updated the toolbar icon stays the same.
Obviously I've done something to break it - any ideas?
EDIT:
Never mind. I'd overwritten the Application's OnIdle() method and hadn't called the original base class method - that is, CWinApp::OnIdle() - which I guess is where the update gets called most of the time. This code snippet from https://msdn.microsoft.com/en-us/library/3e077sxt.aspx illustrates:
BOOL CMyApp::OnIdle(LONG lCount)
{
// CWinApp's original method is involved in the update message handling!
// Removing this call will break things
BOOL bMore = CWinApp::OnIdle(lCount);
if (lCount == 0)
{
TRACE(_T("App idle for short period of time\n"));
bMore = TRUE;
}
// ... do work
return bMore;
// return TRUE as long as there are any more idle tasks
}
Here's a good article that kinda explains how to do it. Don't use his code example with WM_KICKIDLE though, instead scroll down to the comments section. There are two code samples that explain how to do it better. I quote:
//Override WM_INITMENUPOPUP
void CDialog::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CDialog::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
// TODO: Add your message handler code here
if(pPopupMenu &&
!bSysMenu)
{
CCmdUI CmdUI;
CmdUI.m_nIndexMax = pPopupMenu->GetMenuItemCount();
for(UINT i = 0; i < CmdUI.m_nIndexMax; i++)
{
CmdUI.m_nIndex = i;
CmdUI.m_nID = pPopupMenu->GetMenuItemID(i);
CmdUI.m_pMenu = pPopupMenu;
// There are two options:
// Option 1. All handlers are in dialog
CmdUI.DoUpdate(this, FALSE);
// Option 2. There are handlers in dialog and controls
/*
CmdUI.DoUpdate( this, FALSE );
// If dialog handler doesn't change state route update
// request to child controls. The last DoUpdate will
// disable menu item with no handler
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_1, FALSE );
...
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_Last, TRUE );
*/
}
}
}
See if this helps - http://msdn.microsoft.com/en-us/library/essk9ab2(v=vs.80).aspx

MFC VC++ Custom Checkbox Image

How do I get a 3-state checkbox to use a different bitmap for the Indeterminate state?
I want to change the image used by my 3-state checkboxes to use a different one; the controls are in Win98-style, and the indeterminate state of such checkboxes is difficult to distinguish from disabled checkboxes (this is presumably why they changed this for the WinXP-style controls, but I cannot use those because of other details in my project).
I'm using Visual C++ 2010, and I've defined an 8x8 bitmap in VS's Resource Editor. The bitmap's ID is IDB_INDET_CHECK.
I'm not entirely sure what the standard "technique" for something like this is; I've only really just started getting into manipulating Windows controls and MFC.
My first attempt was to create a class, CTriButton, that derives from CButton, override the DrawItem function, and try to draw it myself. I then used SubclassDlgItem to turn one of the checkboxes in my window into this class (I think?). This... sort of works? The checkbox no longer appears, and if I click on where it should be, an empty checkbox frame appears, but nothing else happens (and the debug message in my code is not being sent).
Here's the relevant code, though I'm not sure any of this is right. First, code from my window's OnInitDialog.
BOOL CAffixFilterDlg::OnInitDialog() // CAffixFilterDlg is my CDialog-derived window
{
CDialog::OnInitDialog(); // call basic version
// subclass a CButton-derived control with CTriButton
if ( CBipedHead.SubclassDlgItem(IDC_HEAD, this) ) // CBipedHead is a CTriButton member of CAffixFilterDlg, IDC_HEAD is a checkbox
SetWindowLong(CBipedHead.m_hWnd, GWL_STYLE, CBipedHead.GetStyle() | BS_OWNERDRAW); // set the ownerdraw style
else // subclassing didn't work
_ERROR("Subclassing failed."); // I do not see this error message, so SubclassDlgItem worked?
// initialization continues, but is not relevant...
UpdateWindow();
Invalidate();
return TRUE;
}
Next, the code for my custom button's DrawItem.
void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
_DMESSAGE("Drawing TriButton"); // never see this message
CDC dc;
dc.Attach(lpDrawItemStruct->hDC); //Get device context object
int nWidth = GetSystemMetrics(SM_CXMENUCHECK);
int nMargin = ( nWidth - 8 ) / 2;
CRect textRt = lpDrawItemStruct->rcItem;
textRt.right = textRt.right - nWidth - nMargin;
CString text;
GetWindowText(text);
UINT textDrawState = DST_TEXT;
if ( lpDrawItemStruct->itemState & ODS_DISABLED )
textDrawState |= DSS_DISABLED;
dc.DrawState(CPoint(textRt.left, textRt.top), textRt.Size(), text, textDrawState, TRUE, 0, (CBrush*)NULL);
CRect rt = lpDrawItemStruct->rcItem; // initial rect is for entire button
rt.left = rt.right - nWidth; // set left margin
LONG center = ( rt.bottom + rt.top ) / 2;
rt.top = center - nWidth/2;
rt.bottom = center + nWidth/2;
UINT checkDrawState = DFCS_BUTTONCHECK;
if ( lpDrawItemStruct->itemState & ODS_DISABLED )
checkDrawState |= DFCS_INACTIVE;
if ( lpDrawItemStruct->itemState & ODS_CHECKED )
checkDrawState |= DFCS_CHECKED;
else if ( GetCheck() == BST_INDETERMINATE ) {
_VMESSAGE("Indeterminate; custom draw.");
CBitmap indet_check = CBitmap();
indet_check.LoadBitmap(IDB_INDET_CHECK);
CPoint pt = CPoint(rt.left + nMargin, rt.top + nMargin);
CSize sz = CSize(8, 8);
dc.DrawState(pt, sz, &indet_check, DST_BITMAP|DSS_NORMAL);
}
dc.DrawFrameControl(rt, DFC_BUTTON, checkDrawState);
}
In OnInitDialog() you need to call InvalidateRect() after changing the window style otherwise it doesn't know it needs to be redrawn. It's also a good idea to call UpdateWindow() after changing window styles. Some information is usually cached by the common controls and won't acknowledge the change until UpdateWindow() has been called.
In DrawItem() you are responsible for rendering all states of the control. You should not call CButton::DrawItem() as it does nothing. Try something like the following:
void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CBitmap indet_check
_DMESSAGE("Drawing TriButton"); // I never see this message
int checkState = GetCheck();
if ( checkState == BST_CHECKED )
{
indet_check.LoadBitmap(IDB_INDET_CHECK);
}
else if ( checkState == BST_UNCHECKED )
{
indet_check.LoadBitmap(IDB_INDET_UNCHECKED);
}
else if ( checkState == BST_INDETERMINATE )
{
indet_check.LoadBitmap(IDB_INDET_INDETERMINATE);
}
// ... rest of your drawing code here ...
// don't forget to draw focus and push states too ;)
}
Addendum:
I can't believe I missed this first time around but your call to SubclassDlgItem is probably not having the desired effect. This call causes messages intended for the button to be processed by the controls parent window first. Because the default implementation of DrawItem in CWnd (the superclass of CDialog) does nothing the message never gets passed to the control.
Replace this with the following snippet and everything should be ok:
HWND hWndButton;
GetDlgItem(IDC_HEAD, &hWndButton);
CBipedHead.SubclassWindow(hWndButton);
Two side notes here:
It's usually not a good idea to use the same naming convention for both classes and class members. It makes for a confusing read.
I'm guessing you are always compiling and running in release mode. If you are - don't. This prevents assertions from being thrown and letting you know something is wrong.
Not the answer, but an answer: this custom CCheckBox I found more-or-less enables what I want. It doesn't, by default, allow 3 states, but I fixed that up with some of my own tweaks. I'm not 100% sure it works out of the box (I've had some issues, that don't seem to be due to my edits, but I can't be sure), but it was the solution I've used. I'm not going to call this the answer, though, in case someone can spy what was wrong with my code and wants to illuminate me.

My MFC Application Freezes if I Switch to Another App

Commence V 2.0 of this question.
My VC++ MFC application compiles and runs just fine. That is, until I switch to another window. As soon as my program loses focus, it freezes; I cannot switch back to it and if I move a window that is in front of it, my app window displays white in the space that the other window was covering.
Since I first posted this question I have been able to pinpoint what portion of my program is causing this behavior, but I still don't know what lines of code are wrong or why.
The main dialog of my MFC application contains a CTabCtrl called m_MainTabControl. This main tab control holds onto two tabs labeled "Basic Design" and "Advanced Design", each of which are tied to their own dialogs. Both dialogs contain a CTabCtrl with several tabs. And thus my problem is born.
When I first created this tab-within-tab structure, I was having problems with my program freezing up when I tried to switch between inner tabs. Needless to say I was puzzled by this, until I noticed that if I first clicked on a control on one of the inner tabs I could then switch tabs just fine. So without fully understanding what the problem was, I set the focus to the first inner tab of the first inner tab on program startup. Problem half solved. When I ran the program I could click around on the first set of inner tabs just fine; if I switched to the second outer tab and tried clicking around its inner tabs without first clicking on a dialog control, it would freeze again. So I made a Focus function to focus the currently selected inner tab of the currently selected outer tab and called that in my tab change event (the one that goes off when you click on another tab).
That is how I got to the point I was at when I first asked this question. I thought my program was working just fine, until I played around with it a bit more and noticed it wasn't playing nicely when I flipped back and forth between it and another window or program. More specifically, as soon as my program goes out of focus some process goes haywire and takes up an entire CPU's worth of processing.
And now, the reason I explained my little tab structure and focus problems in such great detail: after much experimentation, I found that if I commented out the second tab on my outer tab structure (i.e. the "Advanced Design" dialog and all of its little children tabs) the remaining bit of my program runs fine, and I can switch to another window and back without a fight. "Great," I thought, "that's only about 90% of my program that I just commented out to get this working. Let's try and pare it down further." I put the "Advanced Design" tabbed dialog back in, but commented out the tabbed dialogs created by the tab control within "Advanced Design" and everything still worked fine. One by one I put back the tabbed dialogs belonging to "Advanced Design"'s tab control and every time I reintroduced The Error regardless of what tab I uncommented. I should also point out that one of the first tabs I tried putting back in (alone, of course) was my "Margins" tab, which is a MarginDlg class that I ALSO USE UNDER MY "Basic Design" TAB WITHOUT TROUBLE. This leads me to believe that there is something specific I have to do when creating tabbed dialogs within tabbed dialogs that I am not doing, or am doing incorrectly, kind of like how I had to mess with focusing to make it work at first.
I would be EXTREMELY grateful for any light that can be shed on the situation. I am including what I think are relevant snippets of code; as always, let me know if more is needed.
variable declarations from main dialog's .h file:
//tab stuff
CTabCtrl m_MainTabControl;
vector<CDialog*> m_tabPages;
SimpDesDlg* simpDesDlg;
AdvDesDlg* advDesDlg;
When my application starts up, it creates my main dialog, and the OnInitDialog() is called:
(everything above the TODO: comment is default VS stuff dealing with the stupid about dialog)
BOOL CspAceDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
///////////////////////////////////////
DrawResultsArea();
DrawTabs();
theApp.Calculate();
Focus();
//simpDesDlg->Focus();
//DrawToolbar();
return FALSE; // return TRUE unless you set the focus to a control
}
DrawTabs() is where I create the tabs for my outer tab control:
void CspAceDlg::DrawTabs()
{
simpDesDlg = new SimpDesDlg;
m_tabPages.push_back(simpDesDlg);
advDesDlg = new AdvDesDlg;
m_tabPages.push_back(advDesDlg);
// create a tcItem to hold the name of each tab during creation
// and then get inserted, and arrays holding the tab names and IDs of
// the dialogs they refer to
TC_ITEM tcItem;
PSTR pszTabNames[] = {"Basic Design", "Advanced Design"};
UINT pszTabItems[] = {IDD_SIMPLEDESIGNTAB, IDD_ADVANCEDDESIGNTAB};
//every member of m_tabPages[] will become a tab
for (int i = 0; i < int(m_tabPages.size()); i++)
{
//set up the tab name
tcItem.mask = TCIF_TEXT;
tcItem.pszText = pszTabNames[i];
tcItem.cchTextMax = int(strlen(pszTabNames[i]));
//insert the new tab into the tab control and create the dialog window
m_MainTabControl.InsertItem(i, &tcItem);
m_tabPages[i]->Create(pszTabItems[i], &m_MainTabControl);
}
//redraw so that the dialogs don't appear in upper left corner and cover the tabs
CRect tabRect, itemRect;
int nX, nY, nXc, nYc;
m_MainTabControl.GetClientRect(&tabRect);
m_MainTabControl.GetItemRect(0, &itemRect);
nX=itemRect.left;
nY=itemRect.bottom+1;
nXc=tabRect.right-itemRect.left-1;
nYc=tabRect.bottom-nY-1;
m_tabPages[0]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_SHOWWINDOW);
for(int nCount=1; nCount < int(m_tabPages.size()); nCount++){
m_tabPages[nCount]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_HIDEWINDOW);
}
}
Main dialog's Focus() method:
void CspAceDlg::Focus()
{
int curSel = m_MainTabControl.GetCurSel();
m_tabPages[curSel]->SetFocus();
//if it's the basic design, we need to focus its first tab
if (curSel == 0)
{
simpDesDlg->Focus();
}
//if it's the advanced design, we need to focus its first tab
else if (curSel == 1)
{
advDesDlg->Focus();
}
}
code for m_MainTabControl's tab selection change event:
void CspAceDlg::OnTcnSelchangeBuildtabs(NMHDR *pNMHDR, LRESULT *pResult)
{
for (int i = 0; i < int(m_tabPages.size()); i++)
{
m_tabPages[i]->ShowWindow(m_MainTabControl.GetCurSel() == i ? SW_SHOW :
SW_HIDE);
}
Focus();
*pResult = 0;
}
SimpDesDlg and AdvDesDlg's tabs use identical code except for tab initialization (just creating different tabs for each) so here's the code for AdvDesDlg:
OnInitDialog():
BOOL AdvDesDlg::OnInitDialog()
{
CDialog::OnInitDialog();
DrawTabs();
return false;
}
adding in the tabbed dialogs:
void AdvDesDlg::DrawTabs()
{
//Make the dialogs for the tabs
antennaDlg = new AntennaDlg;
commSEDlg = new CommSEDlg;
encryptorDlg = new EncryptorDlg;
marginDlg = new MarginDlg;
miscDlg = new MiscDlg;
transRecDlg = new TransRecDlg;
//add them all to the tabPages vector
m_tabPages.push_back(antennaDlg);
m_tabPages.push_back(commSEDlg);
m_tabPages.push_back(encryptorDlg);
m_tabPages.push_back(marginDlg);
m_tabPages.push_back(miscDlg);
m_tabPages.push_back(transRecDlg);
//m_tabPages[0] = new AntennaDlg;
//m_tabPages[1] = new CommSEDlg;
//m_tabPages[2] = new EncryptorDlg;
//m_tabPages[3] = new MarginDlg;
//m_tabPages[4] = new MiscDlg;
//m_tabPages[5] = new TransRecDlg;
//antennaDlg = (AntennaDlg*) m_tabPages[0];
//commSEDlg = (CommSEDlg*) m_tabPages[1];
//encryptorDlg = (EncryptorDlg*) m_tabPages[2];
//marginDlg = (MarginDlg*) m_tabPages[3];
//miscDlg = (MiscDlg*) m_tabPages[4];
//transRecDlg = (TransRecDlg*) m_tabPages[5];
// create a tcItem to hold the name of each tab during creation
// and then get inserted, and arrays holding the tab names and IDs of
// the dialogs they refer to
TC_ITEM tcItem;
PSTR pszTabNames[] = {"Antenna", "Comm Support", "Encryptor", "Margins", "Misc", "Trasmitter/Receiver"};
UINT pszTabItems[] = {IDD_ANTENNATAB, IDD_COMMSETAB, IDD_ENCRYPTORTAB, IDD_MARGINTAB, IDD_MISCTAB, IDD_TRANSRECTAB};
//every member of m_tabPages[] will become a tab
for (int i = 0; i < int(m_tabPages.size())/*(sizeof(m_tabPages)/sizeof(m_tabPages[0]))*/; i++)
{
//set up the tab name
tcItem.mask = TCIF_TEXT;
tcItem.pszText = pszTabNames[i];
tcItem.cchTextMax = int(strlen(pszTabNames[i]));
//insert the new tab into the tab control and create the dialog window
advTab.InsertItem(i, &tcItem);
m_tabPages[i]->Create(pszTabItems[i], &advTab);
}
//redraw so that the dialogs don't appear in upper left corner and cover the tabs
CRect tabRect, itemRect;
int nX, nY, nXc, nYc;
advTab.GetClientRect(&tabRect);
advTab.GetItemRect(0, &itemRect);
nX=itemRect.left;
nY=itemRect.bottom+1;
nXc=tabRect.right-itemRect.left-1;
nYc=tabRect.bottom-nY-1;
//m_tabPages[0]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_SHOWWINDOW);
for(int nCount=/*1*/0; nCount < int(m_tabPages.size())/*(sizeof(m_tabPages)/sizeof(m_tabPages[0]))*/; nCount++){
m_tabPages[nCount]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_HIDEWINDOW);
}
}
And the Focus() and tab changing:
void AdvDesDlg::Focus()
{
this->SetFocus();
for (int i = 0; i < int(m_tabPages.size())/*(sizeof(m_tabPages)/sizeof(m_tabPages[0]))*/; i++)
{
m_tabPages[i]->ShowWindow(advTab.GetCurSel() == i ? SW_SHOW :
SW_HIDE);
}
int curSel = advTab.GetCurSel();
m_tabPages[curSel]->SetFocus();
}
void AdvDesDlg::OnTcnSelchangeAdvDesign(NMHDR *pNMHDR, LRESULT *pResult)
{
for (int i = 0; i < int(m_tabPages.size())/*(sizeof(m_tabPages)/sizeof(m_tabPages[0]))*/; i++)
{
m_tabPages[i]->ShowWindow(advTab.GetCurSel() == i ? SW_SHOW :
SW_HIDE);
}
int curSel = advTab.GetCurSel();
m_tabPages[curSel]->SetFocus();
*pResult = 0;
}
Debug your app, get it into this state, then go Debug / Break All. Find the main thread in the Threads window, then look at the call stack. Somewhere in the call stack will be your code that's causing the hang.
Note that in order to get a sensible call stack you'll need to point Visual Studio at Microsoft's symbol servers; see http://msdn.microsoft.com/en-us/library/b8ttk8zy.aspx
The behavior you describe usually occurs when you are running some process without allowing the message pump to run.
What does your app do? I assume you start it, and then either hit a button or select a menu item to start some process.
If the program behaves normally when you first start it (before you click to start your processing), but then behaves as you describe after starting the process, then that's your problem.
You'll have to move that processing into another thread to allow the MFC GUI to remain responsive.
I just stumbled about a very similar situation. My setup:
A CPropertySheet containing several CPropertyPages
A CTabCtrl inside one CPropertyPage
Several CDialogs as part of the CTabCtrl
Using a control in one specific CDialog and then switching to another application (e.g. Visual Studio via a hit breakpoint) will stall my application (the respective CPU core ends up at 100% load)
Using a control in the same specific CDialog but then switching to another CPropertyPage before switching to another application will not result in any problems
After some research I found this Knowledge Base article which hinted me to a solution. The specific CDialog which triggered the stall had the line EXSTYLE WS_EX_CONTROLPARENT in its resource definition. Removing the line solved the problem.
This prevents being able to tab into the CDialog, but that problem is slightly less serious and I might come back to it another day.
I had a similar issue. I had multiple nested windows, some with the WS_EX_CONTROLPARENT style, some not. The thing is: Either all inner windows have to have this style, or none of them (at least apparently).