Get button focus - MFC - mfc

I have a VC++ MFC dialog application and in my OnTimer function I am just trying to determine which button in my dialog currently has focus.
Here is some pseudocode of what I am trying to accomplish....
CDialog::OnTimer()
{
CButton *btn = GetButtonOnFocus();
int btnID = btn->GetDlgCtrlID();
}

I haven't tried it, but this should work:
CWnd * pFocus = GetFocus();
int btnID = 0;
if (pFocus != NULL && pDialog->IsChild(pFocus))
btnID = pFocus->GetDlgCtrlID();
This won't restrict the result to buttons only - to do that, you need to use GetClassName and compare to "button".

Related

How to handle message send from view class to dialog?

I have SDI application that hand view, doc and mainframe. In view class, I have button to open another dialog, let say Chartering dialog. I would like to open that dialog and send initial value from view to assign some variable at dialog, but I can not catch message event at dialog class. Below as my code:
// button onclick to show new dialog
charteringDlg = new CharteringDlg();
// show chartering dialog
if(charteringDlg->Create(IDD_DIALOG_CHATTERING, GetDesktopWindow()))
{
bChartering = true;
charteringDlg->MoveWindow(900,300,450,300);
charteringDlg->ShowWindow(SW_SHOW);
int temp = 12;
GetMain()->SendMessage(UWM_MYMESSAGE_CHARTERING, 0,(LPARAM)&temp);
}
and in chartering dialog I handle message like below
ON_MESSAGE(UWM_MYMESSAGE_CHARTERING, &CharteringDlg::OnSetShowTemp)
chartering function
LRESULT CharteringDlg::OnSetShowTemp(WPARAM, LPARAM lParam)
{
int * s = (int *)lParam;
return 0;
}
I set break point at OnSetShowTemp() function but it cannot jump there.
Any idea would be great appreciated.
For assigning an initial value to one of your dialog's members you don't need to send it a message.
You can just assign the value directly:
So instead of
GetMain()->SendMessage(UWM_MYMESSAGE_CHARTERING, 0,(LPARAM)&temp);
you should have something like:
charteringDlg->thevalueorwhatever = 12;
And BTW:
GetMain()->SendMessage(UWM_MYMESSAGE_CHARTERING, 0,(LPARAM)&temp);
is wrong anyway, you should send the message to the dialog and not to the main window:
charteringDlg->SendMessage(UWM_MYMESSAGE_CHARTERING, 0,(LPARAM)&temp);

CTabCtrl ItemAction & ItemState

I’ve created my own CXTabCtrl that extends CTabCtrl and override the DrawItem Function.
During the phase of rewriting the DrawItem Function, I wasn’t able to differentiate between this two states of CTabCtrl Item:
CTabCtrl item is selected and have focus.
CTabctrl item is selected but doesn’t have focus.
By focus I mean the Focus rectangle is not drawing. Here are two images that will help identify the two states:
Here’s the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
Here’s a part of the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
BOOL bFontSuccess = FALSE;
CFont* def_font = NULL;
CFont font_italic;
TC_ITEM tci;
CRect rect(lpDrawItemStruct->rcItem);
wchar_t szTabText[256];
wmemset(szTabText,_T('\0'),256);
RECT rectComplet;
GetClientRect(&rectComplet);
CBrush brtmp(ColorCategoryBackgroundTop);
int nbItem = GetItemCount();
tci.mask = TCIF_TEXT;
tci.pszText = szTabText;
tci.cchTextMax = sizeof(szTabText) -1;
GetItem(lpDrawItemStruct->itemID, &tci);
BOOL bSelect = (lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE));
BOOL bfocus = (lpDrawItemStruct->itemState & ODS_FOCUS) &&
(lpDrawItemStruct->itemAction & (ODA_FOCUS | ODA_DRAWENTIRE));
if (bSelect)//Draw In a Specific Way
if (bFocus) //Draw In a Specific Way
}
So, I would be grateful if someone can describe the proper way to detect the two states of a CTabCtrl Item “Selected & Focused”, “Selected & But not focused”
For a standard tab control, the UI will not always draw the focus rectangle. To see the focus rectangle, the tab control must have WS_TABSTOP flag.
The focus rectangle will then be visible when user clicks the Tab key to go through the dialog's controls, or when Alt key is pressed and tab control has focus.
The focus rectangle should be drawn automatically for owner draw tab control when applicable. Make sure WS_TABSTOP is set for tab control (in dialog editor, go to tab control's properties and set "Tabstop = true")
BOOL focused = selected && (GetFocus() == this); will always be TRUE when user clicks on the tab control. ODS_NOFOCUSRECT will indicate if focus rectangle is not requested by the UI. See the example below.
Side note, sizeof(szTabText) returns the wrong value for wchar_t. Use _countof(szTabText) or sizeof(szTabText)/sizeof(*szTabText)
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT di)
{
CDC* pDC = CDC::FromHandle(di->hDC);
TC_ITEM tci;
wchar_t text[256] = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = _countof(text);
GetItem(di->itemID, &tci);
BOOL selected = di->itemState & ODS_SELECTED;
BOOL focused = selected && (GetFocus() == this);
//The UI may not be drawing focus rectangle, even if we click on the tab
if(di->itemState & ODS_NOFOCUSRECT)
focused = FALSE;
CString str;
if(selected) str += L"SEL ";//indicate selected
if(focused) str += L"FOC ";//indicate focused
CRect rect(di->rcItem);
pDC->TextOut(rect.left, rect.top, str);
}

MFC CdockablePane Change Look

I'm upgrading an old MFC project to use the MFC Feature Pack and Ribbon. I changed a pane derived from CControlBar to be derived from CDockablePane and it works great, however it still looks old and does not blend with the ribbon look and theme. The Pane also has buttons that still look old.
How do I change the look, feel or theme of a CDockablePane derived object and buttons derived from CButton?
I'm using the following code to draw the button, is the best way to simply change the background?
void CFolderButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT uState=DFCS_BUTTONPUSH;
if( lpDrawItemStruct->itemState & ODS_SELECTED )
{
uState|=DFCS_PUSHED;
}
//CDC dc;
CDC* dc = CDC::FromHandle(lpDrawItemStruct->hDC);
dc->DrawFrameControl(&lpDrawItemStruct->rcItem,DFC_BUTTON,uState);
if( !IsWindowEnabled() )
{
dc->SetTextColor(::GetSysColor(COLOR_3DSHADOW));
}
CString csText;
GetWindowText(csText);
if (m_iDisplayType != 2 || !m_hIcon)
{
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
lf.lfHeight = m_iFontSize;
strcpy(lf.lfFaceName, "Tahoma Bold");
VERIFY(font.CreateFontIndirect(&lf));
CFont* def_font = dc->SelectObject(&font);
RECT buttonRect = lpDrawItemStruct->rcItem;
buttonRect.left += 10;
buttonRect.right += 10;
if (m_iDisplayType != 1 || !m_hIcon) //text & Icon
{
buttonRect.left += 30;
buttonRect.right += 30;
}
dc->DrawText(csText,&buttonRect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
dc->SelectObject(def_font);
font.DeleteObject();
}
if (m_hIcon && m_iDisplayType != 1)
{
CSize czText = dc->GetTextExtent(csText);
dc->DrawIcon(0,0,m_hIcon);
}
}
The following image shows the contrast between the buttons and the ribbon:
The new MFC uses a CMFCVisualManager. And drawing using the current styles isn't easy.
This class is virtual and used for all drawings in the specific style of the application.
Just look into the source of CMFCToolBarButton::OnDraw and see how all kinds of Buttons and text are drawn.
PS: May be it is easier to create a new CMFCToolBar if there are just Buttons and controls in it.
PPS: Or embed a new CMFCToolBar in the CDockingPane.

How to customize CMFCMenuBar in an MFC application

Is there a way to create dynamic menu items in the menu bar of an MFC applications created with visual studio 2008 style with menus not ribbon, I have plugins that add their menus dynamically how can I add them to that menu??? I see
//this is the menubar that i want to update
CMFCMenuBar m_wndMenuBar;
At first you have to create a new submenu. It can be filled by hand or loaded from resources.
Then create a new menu button and attach submenu to it. After that update the menu bar. That's it.
CMenu menu;
if(menu.LoadMenu(IDR_MY_MENU))
{
m_wndMenuBar.InsertButton(CMFCToolBarMenuButton(0, menu, -1, _T("&MyMenu")), -1);
m_wndMenuBar.AdjustLayout();
m_wndMenuBar.AdjustSizeImmediate();
}
P.S. menu may be local because CMFCToolBarMenuButton constructor copy content from it.
You probably need to override:
CMDIFrameWndEx::OnShowPopupMenu(CMFCPopupMenu* pMenuPopup)
and put a placeholder menu item where you need a dynamic menu, then replace the placeholder menu item with your dynamic menu.
For an example look into the source code shipping with MFC:
afxframeimpl.cpp
BOOL CFrameImpl::OnShowPopupMenu(CMFCPopupMenu* pMenuPopup, CFrameWnd* /*pWndFrame*/)
The ideal way is probably to completely build a CMenu object, and then assign it to the menu bar: This makes the code almost compatible with the previous versions of VisualStudio.
Declare a pointer to an instance of CMenu in CMainFrame definition:
CMenu *m_pMainMenu
Set it to NULL in CMainFrame constructor:
m_pMainMenu = NULL;
In CMainFrame::OnCreate(), create an instance of CMenu assuming it was NULL before:
SAFE_DELETE(m_pMainMenu);
m_pMainMenu = new CMenu;
Modify it at will with:
pMainMenu->LoadMenu(),
pMainMenu->RemoveMenu(),
pMainMenu->AppendMenu(),
pMainMenu->InsertMenu(),
...
Finally, associate the menu toolbar with this new menu by using CreateFromMenu:
m_wndMenuBar.CreateFromMenu(m_pMainMenu->m_hMenu);
Don't forget to release m_pMainMenu in CMainFrame destructor to avoid leaks:
SAFE_DELETE(m_pMainMenu)
Note: This is a very handy and safe macro:
#define SAFE_DELETE(a) { if (a) { delete(a); a=NULL; } }
You can use UPDATE_COMMAND_UI in property view window.
Let's say, you put a 'Text Capture' menu with 'ID_TEXTCAPTURE', and then you want to change the menu with 'Stop Capture'.
-in message map
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWndEx)
ON_WM_CREATE()
ON_UPDATE_COMMAND_UI(ID_TEXTCAPTURE, &CChildFrame::OnUpdateTextcapture)
END_MESSAGE_MAP()
-event handler
void CChildFrame::OnUpdateTextcapture(CCmdUI *pCmdUI)
{
if(pCmdUI->m_pMenu)
{
int pos;
CMenu* mmenu = pCmdUI->m_pMenu;
pos = FindMenuItem(mmenu, _T("Text Capture(&C)"));
if (pos > -1 && IsCapture == TRUE)
{
UINT id = mmenu->GetMenuItemID(pos);
//change menu text as 'Stop Capture'
mmenu->ModifyMenu(id, MF_BYCOMMAND, id, _T("Stop Capture(&S)"));
IsCapture = TRUE;
}
}
}
CChildFrame::FindMenuItem(CMenu* Menu, LPCTSTR MenuString)
{
ASSERT(Menu);
ASSERT(::IsMenu(Menu->GetSafeHmenu()));
int count = Menu->GetMenuItemCount();
for (int i = 0; i < count; i++)
{
CString str;
if (Menu->GetMenuString(i, str, MF_BYPOSITION) && str.Compare(MenuString) == 0)
return i;
}
return -1;
}

How do I detect if a modeless CDialog has been closed?

I have followed this question to make a non-modal/modeless dialog:
How to display a non-modal CDialog?
I'm using MFC/C++ in VS2008. I'm more fluent with C# and .net than with MFC and C++.
I have a menu item in my form that launches the dialog. There can only be one instance of the dialog opened. The dialog displays fine. I can close it by clicking the X in the corner and it closes when I close the main form. The problem I am having is the dialog cannot be opened again after I click the X to close the dialog. I know it is because the pointer is never set back to NULL.
I have this in my form's header file:
CChildDialog *m_pDialog;
I have this part in my form's constructor:
m_pDialog = NULL;
When clicking on a menu item I have this code in the menu item's method (I modified it from the other SO answer because I only want one instance of the dialog opened):
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
m_pDialog->ShowWindow(SW_SHOW);
}
Now I know I need to execute this part and set the pointer to NULL, but I don't know where to put this:
// Delete the dialog once done
delete m_pDialog;
m_pDialog = NULL;
Do I need to keep monitoring if the dialog has been disposed? Is there an event triggered to the parent form when the dialog is closed?
If you want to recycle the contents of the window after closing it with X, you have to handle the WM_CLOSE message in your dialog:
void CChildDialog::OnClose()
{
ShowWindow(SW_HIDE);
}
Then in the code that opens the window:
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
}
m_pDialog->ShowWindow(SW_SHOW); //moved outside the if(m_pDialog == NULL)
Hope it can help
If you want to delete the modeless dialog, then just do so.
If you want to delete the dialog's object when the user closed the modeless dialog you might take a look at WM_PARENTNOTIFY. If a child window is destroyed and the child windows has not the extended window style WS_EX_NOPARENTNOTIFY set, then windows sends a WM_PARENTNOTIFY with wParam=WM_DESTROY to the parent window. You should implement a handler for that message in the parent window and check if it's the modeless dialog that is being destroyed.
I had the question drafted up and was ready to post it, but then I had an idea and ended up solving my own problem. So for anyone else who has an issue with detecting the closing of a modeless dialog, this is what I did:
void Form1::MenuItemMethod()
{
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
m_pDialog->ShowWindow(SW_SHOW);
}
else
{
// cannot check if visible at the beginning of method because
// pointer could be NULL and will throw an exception
if(m_pDialog->IsWindowVisible())
{
return;
}
m_pDialog->DestroyWindow();
m_pDialog = NULL;
MenuItemMethod();
}
}
I just ended up checking if the modeless dialog is visible after clicking on the form's menu item again. If it is visible, don't do anything. If not, destroy the existing non-visible dialog, set the pointer to NULL, and recursively call the method again. Since the pointer is now NULL, it should recreate the dialog normally and then return to normal operation.
you have to delete the memory in PostNcDestroy like this
void CChildDialog ::PostNcDestroy()
{
CDialog::PostNcDestroy();
GetParent()->PostMessage(WM_WIN_CLOSED,0,0);
delete this;
}
and send a user defined message to the parent window that your window is closed. In the parent window add a message handler for WM_WIN_CLOSED like
LRESULT CMainDialog::OnMyMethod(WPARAM wParam, LPARAM lParam)
{
m_pDialog = NULL;
return 0;
}