CMFCPropertyGrid: How to catch dropdown event of combobox property - c++

I have CMFCPropertyGrid control to which I have added a Combobox property with CMFCPropertyGridProperty::AddOption():
foreach(auto i, list)
{
str.Format(_T("<%s> %s"), i.first, i.second);
pProp->AddOption(str);
}
Now I need to do some code when the user drops down the list in this CMFCPropertyGrid.
I would use CBN_DROPDOWN if it were a combobox control in dialog window (it has ID). But how can I do that in case of CMFCPropertyGrid?

For a combobox-style CMFCPropertyGridProperty, the framework calls the OnClickButton member when the user clicks the down-arrow button near the top-right of the control (which causes the dropdown list to appear).
So, create a class for your control (derived from CMFCPropertyGridProperty) and override the OnClickButton member to add the code you want to run. Here's an outline of what you could do:
class MyComboControl : public CMFCPropertyGridProperty
{
public:
// Constructor: add some options ...
MyComboControl(void) : CMFCPropertyGridProperty(L"Choice:", _variant_t(L""), L"Description Text") {
AllowEdit(FALSE);
for (int i = 0; i < 3; ++i) {
CString text;
text.Format(L"Option #%d", i);
AddOption(text.GetString());
if (i == 0) SetValue(text.GetString());
}
}
// Override to handle the dropdown activation ...
void OnClickButton(CPoint pt) override {
//... Add any pre-drop code here.
CMFCPropertyGridProperty::OnClickButton(pt); // Call base class
//... Add any post-drop code here
}
};

MFC will notify the parent window for property changes throught AFX_WM_PROPERTY_CHANGED
Sent to the owner of the property grid
control (CMFCPropertyGridCtrl) when the user changes the value of
the selected property.
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_REGISTERED_MESSAGE(AFX_WM_PROPERTY_CHANGED, OnPropertyChanged)
END_MESSAGE_MAP()
afx_msg LRESULT CMyDialog::OnPropertyChanged(WPARAM wparam, LPARAM lparam)
{
if (!lparam)
return 0;
auto prop = reinterpret_cast<CMFCPropertyGridProperty*>(lparam);
if (prop != m_pProp)
{
auto str_variant = prop->GetValue();
CString str;
if(str_variant.vt == VT_BSTR)
str = CString(str_variant.bstrVal);
}
return 0;
}
If you have multiple control with the grid control, you want to declare pProp as a class member m_pProp so its value can be tested.

Related

How to create a CDockablePane in a CFrameWnd with C++ in MFC

At first I called the Create method of the CFrameWnd within another class.
Then I continued with the Create method of CDockablePane with the FrameWnd as the pParentWnd parameter.
The second Create was not successful, an assertion occured in the following code:
void CMFCDragFrameImpl::Init(CWnd* pDraggedWnd)
{
ASSERT_VALID(pDraggedWnd);
m_pDraggedWnd = pDraggedWnd;
CWnd* pDockSite = NULL;
if (m_pDraggedWnd->IsKindOf(RUNTIME_CLASS(CPaneFrameWnd)))
{
CPaneFrameWnd* pMiniFrame = DYNAMIC_DOWNCAST(CPaneFrameWnd, m_pDraggedWnd);
pDockSite = pMiniFrame->GetParent();
}
else if (m_pDraggedWnd->IsKindOf(RUNTIME_CLASS(CPane)))
{
CPane* pBar = DYNAMIC_DOWNCAST(CPane, m_pDraggedWnd);
ASSERT_VALID(pBar);
CPaneFrameWnd* pParentMiniFrame = pBar->GetParentMiniFrame();
if (pParentMiniFrame != NULL)
{
pDockSite = pParentMiniFrame->GetParent();
}
else
{
pDockSite = pBar->GetDockSiteFrameWnd();
}
}
m_pDockManager = afxGlobalUtils.GetDockingManager(pDockSite);
if (afxGlobalUtils.m_bDialogApp)
{
return;
}
ENSURE(m_pDockManager != NULL); <-----------------------
}
Somehow a docking manager seems to be missing. Is it possible that CFrameWnd is not suitable for CDockablePane? Or the docking manager needs to be initialized?
Thanks for your help (code snippets are welcome)!
To add a dockable pane to your project, the first step is to derive a new class from CDockablePane and you must add two message handlers for OnCreate and OnSize, and add a member child window as the main content. Your simple CTreePane class should look like this:
class CTreePane : public CDockablePane
{
DECLARE_MESSAGE_MAP()
DECLARE_DYNAMIC(CTreePane)
protected:
afx_msg int OnCreate(LPCREATESTRUCT lp);
afx_msg void OnSize(UINT nType,int cx,int cy);
private:
CTreeCtrl m_wndTree ;
};
int CTreePane::OnCreate(LPCREATESTRUCT lp)
{
if(CDockablePane::OnCreate(lp)==-1)
return -1;
DWORD style = TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT|
WS_CHILD|WS_VISIBLE|TVS_SHOWSELALWAYS | TVS_FULLROWSELECT;
CRect dump(0,0,0,0) ;
if(!m_wndTree.Create(style,dump,this,IDC_TREECTRL))
return -1;
return 0;
}
In the OnSize handler, you should size your control to fill the entire dockable pane client area.
void CTreePane::OnSize(UINT nType,int cx,int cy)
{
CDockablePane::OnSize(nType,cx,cy);
m_wndTree.SetWindowPos(NULL,0,0,cx,cy, SWP_NOACTIVATE|SWP_NOZORDER);
}
To support a dockable pane in your frame, you must first derive from the Ex family of frames (CFrameWndEx, CMDIFrameWndEx, ..) and in the OnCreate handler, you should initialize the docking manager by setting the allowable docking area, general properties, smart docking mode, …etc.
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
CDockingManager::SetDockingMode(DT_SMART);
EnableAutoHidePanes(CBRS_ALIGN_ANY);
...
}void CMainFrame::OnTreePane()
{
if(m_treePane && m_treePane->GetSafeHwnd())
{
m_treePane->ShowPane(TRUE,FALSE,TRUE);
return ;
}
m_treePane = new CTreePane;
UINT style = WS_CHILD | CBRS_RIGHT |CBRS_FLOAT_MULTI;
CString strTitle = _T("Tree Pane");
if (!m_treePane->Create(strTitle, this,
CRect(0, 0, 200, 400),TRUE,IDC_TREE_PANE, style))
{
delete m_treePane;
m_treePane = NULL ;
return ;
}
m_treePane->EnableDocking(CBRS_ALIGN_ANY);
DockPane((CBasePane*)m_treePane,AFX_IDW_DOCKBAR_LEFT);
m_treePane->ShowPane(TRUE,FALSE,TRUE);
RecalcLayout();
}

Ribbon UI dynamic button menu edit

I have an application with a ribbon UI. In this UI a button exists with a menu attached to it. What I wish to do is access the menu from the button handler to dynamically add and remove menu items.
void
CMyScrollView::OnMenuButtonHandler ()
{
// TODO: Add your command handler code here
CMFCRibbonBar *pRibbon = ((CMDIFrameWndEx*)GetTopLevelFrame())GetRibbonBar()
// Control ID_BTN_EDIT_MENU
// This where I would like to isolate and vary menu contents
}
In the CMainFRame window create a handler for the AFX_WM_ON_BEFORE_SHOW_RIBBON_ITEM_MENU message (ON_REGISTERED_MESSAGE).
Check for the Id of the button. Remove all previous items and add the one, you want.
LRESULT CMainFrame::OnBeforeShowRibbonItemMenu(WPARAM,LPARAM lp)
{
CMFCRibbonBaseElement *pElement = reinterpret_cast<CMFCRibbonBaseElement*>(lp);
// Try to get our menu button
switch (pElement->GetID())
{
case ID_RIBBON_DROPDOWN_BUTTON:
{
CMFCRibbonButton *pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pElement);
if (pButton)
{
// MY_LIST copntains members with the ID and the text: m_uiCmdId, m_strTitle
const MY_LIST &list = ....;
if (list.size()!=0)
{
pButton->RemoveAllSubItems();
for (it = list.begin(); it!=list.end(); ++it)
pButton->AddSubItem(new CSomeKindOfRibbonButton(it->m_uiCmdId, it->m_strTitle));
}
}
...

How to make the Tab key input the Tab character to focused edit control instead of navigating among dialog controls? [duplicate]

I have dialog box having buttons and edit box.
When edit control have focus then if I press tab key it moves and focus the button.
I wanted tab key work in such a way that it will not switch focus instead it should work as tab input inside edit control i.e. input to edit box as keys.
The solution is fairly simple, and essentially consists of handling the WM_GETDLGCODE message. This allows a control implementation to fine-tune keyboard handling (among other things).
In MFC this means:
Derive a custom control class from CEdit.
Add the ON_WM_GETDLGCODE message handler macro to the message map.
Implement the OnGetDlgCode member function, that adds the DLGC_WANTTAB flag to the return value.
Subclass the dialog's control, e.g. using the DDX_Control function.
Header file:
class MyEdit : public CEdit {
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg UINT OnGetDlgCode();
};
Implementation file:
BEGIN_MESSAGE_MAP(MyEdit, CEdit)
ON_WM_GETDLGCODE()
END_MESSAGE_MAP
UINT MyEdit::OnGetDlgCode() {
UINT value{ CEdit::OnGetDlgCore() };
value |= DLGC_WANTTAB;
return value;
}
Override the PreTranslateMessage function in your dialog like this :
BOOL CTestThreadDlg::PreTranslateMessage( MSG* pMsg )
{
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB)
{
CWnd* pFocusWnd = GetFocus( );
if (pFocusWnd != NULL && pFocusWnd->GetDlgCtrlID() == IDC_EDIT2)
{
CEdit *pEditCtrl = (CEdit *)pFocusWnd ;
int start, end ;
pEditCtrl->GetSel(start, end) ;
CString str ;
pEditCtrl->GetWindowText(str) ;
str = str.Left(start) + _T("\t") + str.Mid(end) ;
pEditCtrl->SetWindowText(str) ;
pEditCtrl->SetSel(start + 1, start + 1) ;
}
return TRUE ;
}
return CDialog::PreTranslateMessage(pMsg) ;
}
In this example we check if the focus is in the IDC_EDIT2 edit control. You probably have to adapt this to your situation.

accessing variable from the child dialog

I have a child dialog. I initiate other child dialog.
In the second dialog i have the following code for list box.
I need to access the m_selcomponents in the first child dialog. Itried to access this like
dlg->m_selcomponents. But it's getting null. How can i do this? Thank You for help.
int count = m_OutList.GetCount();
for ( i = 0; i <m_OutList.GetCount(); i++)
{
m_OutList.GetText( buf[i], text );
m_selcomponents->Add(text);
}
MSelCFLCompDlg *SelCflCompDlg= new MSelCFLCompDlg(&allcomponents, &m_cflcomponents,m_FileDecimal,this) ;
if(SelCflCompDlg->DoModal()==IDOK) { selectedcomponents.Append(*SelCflCompDlg->m_selcomponents); }
MSelCFLCompDlg::MSelCFLCompDlg(CStringArray *all, CStringArray *sel,int, CWnd* pParent /*=NULL*/)
: CDialog(MSelCFLCompDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(MSelCFLCompDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
m_allcomponents = all;
m_selcomponents = sel;
}
When you create the child dialog box, pass a pointer to the parent dialog box to the child dialog box constructor. If you show us a bit more of your code I can perhaps explain in a bit more detail.

CMFCCaptionMenuButton alternative?

I need to create a caption bar button for a CDockablePane which will call up a menu with various options. I tried to use CMFCCaptionMenuButton and the button and menu show up but the message map methods for the menu ids don't fire. The MFC documentation states that CMFCCaptionMenuButton is meant for internal infrastructure and not really for your code.
So assuming that is what my problem is should I be using a CMFCCaptionBarButton and then making a separate popup menu? Has anyone made a similar caption bar based menu in MFC before?
Here's some slimmed down code snippets in case I just made a stupid mistake in hooking up the events:
BEGIN_MESSAGE_MAP(CDockPane, CDockablePane)
ON_COMMAND(ID_MORPH_BROWSER, OnMorphBrowser)
END_MESSAGE_MAP()
void CDockPane::OnPressButtons(UINT nHit)
{
// only for custom button handling don't call base
// close, maximize, and pin will be handled by default
switch (nHit)
{
case ID_MORPHTEST:
{
CMorphMenuButton* pButton = dynamic_cast<CMorphMenuButton*>(m_arrButtons.GetAt(m_morphIndex));
pButton->ShowMenu(this);
break;
}
}
}
void CDockPane::SetCaptionButtons()
{
CDockablePane::SetCaptionButtons(); // for close, pin etc
m_morphIndex = m_arrButtons.Add(new CMorphMenuButton(ID_MORPHTEST));
}
void CDockPane::OnMorphBrowser()
{
// do stuff on menu item click
}
Edit: Removed previous code no longer in use
Now that the sound of crickets chirping has dwindled in the background I guess I'll post the workaround I currently have in place:
Instead of inheriting and extending CMFCCaptionMenuButton I build my class by extending CMFCCaptionButton. I then create a menu and provide a ShowMenu method to be explicitly called when handling the custom button events as well as overriding GetIconID to return a particular system icon for the button for each menu added to the caption bar ending up with something like this for the example outlined in the question:
#pragma once
// CMorphMenuButton command target
class CMorphMenuButton : public CMFCCaptionButton
{
public:
CMorphMenuButton(UINT nHit);
virtual ~CMorphMenuButton();
virtual CMenuImages::IMAGES_IDS GetIconID (BOOL bHorz, BOOL bMaximized) const;
void ShowMenu(CWnd* pWnd);
private:
CMenu m_dockMenu;
CMenu* m_subMenu;
};
// MorphMenuButton.cpp : implementation file
//
#include "stdafx.h"
#include "MorphMenuButton.h"
// CMorphMenuButton
CMorphMenuButton::CMorphMenuButton(UINT nHit)
: CMFCCaptionButton(nHit)
{
SetMiniFrameButton(); // already defaulted?
m_dockMenu.LoadMenu(IDR_DOCKPANE); // resource ID for dock pane menus
}
CMorphMenuButton::~CMorphMenuButton()
{
m_dockMenu.DestroyMenu();
}
CMenuImages::IMAGES_IDS CMorphMenuButton::GetIconID(BOOL bHorz, BOOL bMaximized) const
{
return CMenuImages::IdArrowForward;
}
void CMorphMenuButton::ShowMenu(CWnd* pWnd)
{
CRect windowRect, buttonRect;
pWnd->GetWindowRect(&windowRect);
buttonRect = GetRect();
CPoint menuPos(windowRect.left + buttonRect.right, windowRect.top + buttonRect.bottom);
m_subMenu = m_dockMenu.GetSubMenu(0);
if (!m_subMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, menuPos.x, menuPos.y, pWnd))
{
DWORD id = GetLastError();
wchar_t errMsg[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, id, 0, errMsg, sizeof(errMsg), 0);
MessageBox(0, errMsg, L"Error", MB_OK);
}
}
The setting of caption bar buttons and handling of click events for both buttons and menus are the same as defined in the question and this works.