How do I create and use a CFormView in an MFC regular DLL? (visual studio 2008) - c++

I recently asked this question which got me started in the right direction - at least for loading the MFC DLL and trying to show a dlg box.
The problem is, the typical dialog box is horrible as a main window for an APP. It is quite simple for me to create a new exe project to do what I want, but the problem is that I have a DLL and the tools just don't seem to allow me to hook up the classes to the windows forms in the resource editor. Thus I can't seem to handle the events that I need.
My questions:
How do I create and display a CFormView (based on an IDD_FORMVIEW I created in a resource editor) in an MFC DLL project?
How do I get the form to show and to process input?
How do I add event/message handlers for that window? (The menu item to do that from the .rc editor is greyed out)
How do I set a menu to the formview? (the properties for the resource in the editor do not let me associate it with a menu resource. (I can't figure out why)
The links I have been looking at are pretty light and ambiguous about how to do it. Most of them assume I can create a mainframe as an MFC single document app via the "wizard" - which is not the case.
Right now I call Create() on the window class I made and pass in the CWnd of the desktop as the parent.
I am not sure I have subclassed the CFormView Correctly. In fact, I am pretty sure I have done little of what I need to do, though I tried to follow the instructions I have seen.
I then call ShowWindow(SW_SHOW), but I still see nothing.
I think this SHOULD be simple. All I want to do is show the form I created in the form editor.
How do I do that and what is the simplest way?
Here is some code - the cpp code that calls it
MainForm *mf = new MainForm();
mf->Create(CWnd::GetDesktopWindow());
mf->ShowWindow(SW_SHOW);
Here is the .h file for the MainForm class
#include "afxcmn.h"
// MainForm form view
class MainForm : public CFormView
{
DECLARE_DYNCREATE(MainForm)
public:
MainForm();
virtual ~MainForm();
public:
virtual BOOL Create(CWnd* pParent);
public:
enum { IDD = IDD_FORMVIEW_MAIN };
#ifdef _DEBUG
virtual void AssertValid() const;
#ifndef _WIN32_WCE
virtual void Dump(CDumpContext& dc) const;
#endif
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
CListCtrl m_SymbolSetList;
};
and here is the cpp for MainForm
#include "stdafx.h"
#include "MainForm.h"
// MainForm
IMPLEMENT_DYNCREATE(MainForm, CFormView)
MainForm::MainForm()
: CFormView(MainForm::IDD)
{
}
MainForm::~MainForm()
{
}
void MainForm::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST_SYMBOLSETS, m_SymbolSetList);
}
BEGIN_MESSAGE_MAP(MainForm, CFormView)
END_MESSAGE_MAP()
// MainForm diagnostics
#ifdef _DEBUG
void MainForm::AssertValid() const
{
CFormView::AssertValid();
}
#ifndef _WIN32_WCE
void MainForm::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
#endif
#endif //_DEBUG
BOOL MainForm::Create(CWnd* pParent)
{
CRect rect;
//pParent->GetClientRect(rect);
return CFormView::Create(NULL, NULL, WS_CHILD | WS_VISIBLE, rect, pParent, 0, NULL);
}

I would investigate creating and showing your window in a separate MFC UI thread. MFC has got its own mechanism for delivering Windows messages to the CWnd dervived objects called message pump and it needs to initialize its internal structures for it to work. I think you need to use framework function call to do it. Try this version of AfxBeginThread:
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority=HREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
and pass your form as a pThreadClass; there is RUNTIME_CLASS macro that will do it for you. You then end the thread from withing the frame code.
As for the message handling, try opening properties for your form in the resource editor and click the Messages button on the top. You can than add your handlers to the messages that you need to handle.
I was able to associate the Menu property with a resource ID of a menu. I am not sure why you are not able to do so. The resource editor might get confused sometimes if you select a different resource in the tree view but your main window displays a different resource.
Regards

Related

CWindowImpl - Create() returns error code 1406

I just tried to setup a small win32 project, and was just about to create a window.
I created a dialog in the resource designer and set up this class for my window:
#pragma once
#include "stdafx.h"
class TTTMainDialog : public CWindowImpl<TTTMainDialog>
{
public:
DECLARE_WND_CLASS(_T("TTTDlg"))
enum { IDD = IDD_TTT_DIALOG };
BEGIN_MSG_MAP_EX(MusicPlayerDialog)
MSG_WM_INITDIALOG(OnInitDialog);
MSG_WM_CLOSE(OnClose);
MSG_WM_DESTROY(OnDestroy);
END_MSG_MAP()
TTTMainDialog();
~TTTMainDialog();
private:
const BOOL OnInitDialog(const CWindow wndFocus, const LPARAM lInitParam);
void OnClose();
void OnDestroy();
};
As you can see, I added the window class declaration, I inherited CWindowImpl, I registered the dialog. I don't think I forgot something here.
In the class which is supposed to create the dialog, I tried to create it like this:
TTTMainDialog myDialog;
HWND handle = myDialog.Create(NULL);
myDialog.ShowWindow(nCmdShow);
However, the Create method does return NULL all the time. I checked the error code with GetLastError(), and it turns out i am getting error code 1406, or "ERROR_TLW_WITH_WSCHILD".
The msdn documentation says the following about this error:
"Cannot create a top-level child window."
I tried to google up on this, but there is not much to find.
If I had to take a guess I would say the problem is caused by some window class name details, but i'm really not sure.
Any advice?
You are trying to build a window class from wrong pieces.
The error is pretty descriptive: you are trying to create a parentless window with a WS_CHILD style and this does not work out.
You get the child style from default template parameter: CWindowImpl -> CWindowImplBaseT -> TWinTraits -> CControlWinTraits. CControlWinTraits is supposed for use with child control windows.
If you are going to use a dialog template (IDD_TTT_DIALOG) then the proper base class is CDialogImpl, which is already prepared to use proper window styles. Also, it has what it takes to create both modal and modeless dialogs. The latter act more like windows and are non-blocking but in the same time take dialog template resource with predefined controls.
Small example for CDialogImpl use
Or, another one

Float (or detach) the tabbed CMDIChildWndEx window from CMDIFrameWndEx,any idea?

In visual studio,all opened editor windows are tabbed in the workspace.when you right click one,there is a "float" option in the menu.if you float a window,the window is detached from the tab and you can drag it anywhere and after all you can dock it back to the tab.
i created a test mdi project,it's not a default behavior.
i overrided the ID_FILE_NEW command and followed the OpenDocumentFile routine,never found out where the framework add the newly created child frame window to the tab.
in the OpenDocumentFile routine,i bumped into a CMFCTabCtrl class and there is a AddTab function.
now the AddTab function:
virtual void AddTab(
CWnd* pTabWnd,
LPCTSTR lpszTabLabel,
UINT uiImageId = (UINT)-1,,
BOOL bDetachable = TRUE
);
virtual void AddTab(
CWnd* pTabWnd,
UINT uiResTabLabel,
UINT uiImageId = (UINT)-1,
BOOL bDetachable = TRUE
);
the Remarks:
If pTabWnd points to an object that is not derived from the CDockablePane Class and
if bDetachable is TRUE, the framework automatically creates a wrapper for the pTabWnd object.
The wrapper makes the pTabWnd object detachable.
By default, the wrapper is an instance of the CDockablePaneAdapter Class.
If the functionality offered by the default wrapper is unacceptable,
use the CMFCBaseTabCtrl::SetDockingBarWrapperRTC method to specify a different wrapper.
still no luck.
i did try to on the fly create a CView and attach it to the CDocument and put that CView in a CDockablePane.so i could switch the old tabbed one to the new dockable one. it's not working yet.
So any thoughts about floating the tabbed CMDIChildWndEx window from CMDIFrameWndEx?

WM_MENUSELECT not handled in CFrameWndEx windows

I'm trying to handle the WM_MENUSELECT message in a VS2008 (and VS2010) SDI project based on a CFrameWndEx main frame window.
I create a simple project from the VS2008 wizard (single document, "MFC standard", "use classic menu" options) which results in something like (after added the WM_MENUSELECT message):
class CMainFrame : public CFrameWnd
{
///...
public:
afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu);
};
and
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_WM_MENUSELECT()
END_MESSAGE_MAP()
///...
void CMainFrame::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
CFrameWnd::OnMenuSelect(nItemID, nFlags, hSysMenu);
}
This works, when putting a breakpoint in CMainFrame::OnMenuSelect it is triggered when the menu is used (in that case everytime)
Replacing CFrameWnd by CFrameWndEx (or by creating a new project with the wizard with the options single document, "MFC standard", "use a menubar and toolbar" options)
class CMainFrame : public CFrameWndEx
{
///....
public:
afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu);
};
and
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWndEx)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
ON_WM_CREATE()
ON_WM_MENUSELECT()
END_MESSAGE_MAP()
///....
void CMainFrame::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
CFrameWndEx::OnMenuSelect(nItemID, nFlags, hSysMenu);
}
The message will never be triggered.
I've even stripped down the project using CFrameWndEx to the strict minimum and I still cannot get the message.
Any idea why ? tips, hints are always welcome.
Thanks.
Max.
The base class must be correct in the macro at the start of the message map (above the ON_WM_MENUSELECT). My guess is that you didn't change it to CFrameWndEx when you changed the base class.
Aaah, the good old times of single-stepping into the source code of MFC! It's always been the key ;-)
CMFCPopupMenu::SetSendMenuSelectMsg() is your new friend.
Just add this line in your CMainFrame::OnCreate() :
CMFCPopupMenu::SetSendMenuSelectMsg(TRUE);
The bottom line is that MFC menus and toolbars aren't the usual wrappers around standard Windows objects. They implement things differently, using a different model.
Now, for the sake of backward compatibility, you can ask those classes to act as their predecessors and send a WM_MENUSELECT, which they don't by default.
Note: There are probably new mechanisms or best practices to manipulate these objects. It's probably worth investigating them rather than forcing a compatibility setting.

Migrated MFC app from VC6 to VS2010, now OnInitDialog() not called for CPropertyPage subclass

I have been tasked with migrating our product's UI to VS2010. It is an MFC app, originally written in VC6. I have performed the following steps:
Converted the VC6 .dsp using VS2010
fixed up compile errors due to stricter VS2010 compiler
Removed all project references to VC6 mfc libs and directories
My problem is that for a dialog object (actually it's a CPropertyPage object), OnInitDialog() is not being called before other methods are. This causes an exception as OnInitDialog() needs to setup member variables.
The dialog class (CPAGEViewDefRecordFields) is subclassed from our own CValidatedPropertyPage, which in turn is derived from the MFC CPropertyPage class. The virtual method OnInitDialog() is present in all subclasses.
In the VS2010 version, when DoModal() is called on the containing property sheet, the OnInitDialog() method of the CPAGEViewDefRecordFields class is not being called. In the VC6 version, it is being called and all works ok.
In VC6, I can see that the message WM_INITDIALOG is sent, and handled in AfxDlgProc(), which in turn then calls OnInitDialog() of the dialog object.
In the VS2010 version, the first message that is processed is WM_NOTIFY, not WM_INITDIALOG.
Unfortunately I have no prior experience in MFC. What I am assuming that something has changed in the behaviour of MFC between the VC6 version and the VS2010 version. However I've not been able to find anything on the net which is similar to this.
Is there another migration step I have missed? Should I have to do something to the resources in the project when doing the migration?
I have checked that the resource is tied to the correct cpp file, as I can double click on the property page, and the IDE takes me to the correct file for the CPAGEViewDefRecordFields class.
If any of you have any ideas, I'd be very grateful.
Thanks!
Chris.
class CPAGEViewDefRecordFields : public CValidatedPropertyPage
{
public:
// Construction
CPAGEViewDefRecordFields(CWnd* pParent,
CXpViewProp* pViewProp,
CFont* pFont = NULL,
UINT nIDCaption = 0,
BOOL bSumOpRequired = TRUE,
BOOL bMinMaxRequired = TRUE,
BOOL bAllRecords = TRUE,
BOOL bShowInitSel = TRUE,
XLong lLimits = 0,
BOOL bSortSelTree = TRUE,
CXpThreshBaseLogProp* pThreshLogProp = NULL);
~CPAGEViewDefRecordFields();
// Dialog Data
//{{AFX_DATA(CPAGEViewDefRecordFields)
enum { IDD = IDD_VIEW_DEF_RECORD_FIELDS };
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPAGEViewDefRecordFields)
virtual BOOL OnInitDialog();
//}}AFX_VIRTUAL
virtual BOOL OnSetActive();
virtual BOOL OnKillActive();
virtual void OnOK();
protected:
...
// Generated message map functions
//{{AFX_MSG(CPAGEViewDefRecordFields)
afx_msg void OnPbRemove();
afx_msg void OnPbAdd();
afx_msg void OnDblclkAvailableFields(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDblclkSelectedFields(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
...
UPDATE:
After some debugging, I can see what I think is the problem. However, not being an MFC programmer, I don't understand it.
I can see that OnInitDialog() is being called for the property sheet, and that a WM_INITDIALOG is then sent from the property sheet to the property pages. however, at some point in the windows internals, a WM_NOTIFY message is being sent, so this is the first message that is received, not the expected WM_INITDIALOG
I've highlighted the points on the stack trace, attached - can anyone explain why this is occuring? Is this normal behaviour - should I be catering for this in the future?
I've actually found a workaround, and that's to have an initialised flag, so that no code is executed until OnInitDialog() has been called. This is not the best solution, and I fear is more of a hack, so i would still appreciated any understanding of these messages. (I'm not an MFC programmer by trade you see!)
thanks!
OnInitDialog is called after all the dialog controls are created and just before the dialog box is displayed.
Thought I'd better answer this.
The answer came from a SO user's comment:
Your workaround with an initialized flag is the same as I would do. It looks like a tree view sends a notification when the tree view is created but your dialog isn't ready yet. You might not know when other controls do the same thing, so you need an initialized flag
The "workaround" is the only way to guarantee that the dialog is ready.

Disable/Enable Ribbon Buttons for MFC Feature Pack

I am using the MFC Feature Pack and I have some buttons on a ribbon bar, instances of CMFCRibbonButton. The problem is that I would like to enable and disable some of them in certain conditions, but at runtime. How can I do this? because there is no specific method for this...I heard that a solution would be to attach/detach the event handlers at runtime, but I do not know how...
When you create the CMFCRibbonButton object you have to specify the associated command ID (see the documentation for the CMFCRibbonButton constructor here). Enabling and disabling of ribbon buttons is then done using the usual command update mechanism in MFC, using the CCmdUI class.
For example, if you have a ribbon button whose command ID is ID_MYCOMMAND and you want to handle this command in your application's view class, you should add these functions to the class:
// MyView.h
class CMyView : public CView {
// ...
private:
afx_msg void OnMyCommand();
afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI);
DECLARE_MESSAGE_MAP()
};
and implement them in the .cpp file:
// MyView.cpp
void CMyView::OnMyCommand() {
// add command handler code.
}
void CMyView::OnUpdateMyCommand(CCmdUI* pCmdUI) {
BOOL enable = ...; // set flag to enable or disable the command.
pCmdUI->Enable(enable);
}
You should also add ON_COMMAND and ON_UPDATE_COMMAND_UI entries to the message map for the CMyView class:
// MyView.cpp
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND(ID_MYCOMMAND, &CMyView::OnMyCommand)
ON_UPDATE_COMMAND_UI(ID_MYCOMMAND, &CMyView::OnUpdateMyCommand)
END_MESSAGE_MAP()
For more information on message maps in MFC, refer to TN006: Message Maps in MSDN.
I hope this helps!
ChrisN gave a pretty perfect answer. You can see an example of exactly how this is done by downloading the VS2008 Sample Pack from here, and opening the MSOffice2007Demo solution.
When running the sample, look at the "Thumbnails" checkbox in the View tab of the ribbon, it's disabled.
This is controlled by CMSOffice2007DemoView::OnUpdateViewThumb which calls pCmdUI->Enable(FALSE);. You can change this to call TRUE or FALSE at runtime to enable/disable the button respectively.