I have seen several similar question on this subject but I can seem to resolve it.
For example, on CodeProject:
https://www.codeproject.com/Messages/2873837/Re-How-to-set-RTL-layout-for-a-CPropertySheet.aspx
And on SO:
RTL layout issue for Property Sheets (MFC)
So, I have a CMFCPropertySheet that is my main application window and it is set to Arabic when the program starts:
The problem, as it the case for other users, is that whilst the pages are correctly set to RTL layout the sheet is not.
What is the correct way to get the sheet itself to display RTL?
I tried to use PreCreateWindow and it made no difference. I tried to use SetProcessDefaultLayout too. No joy.
Ideally, the window style should be changed in OnNcCreate before the window starts creating and positioning its child controls. This way, the child tab, as well as child buttons, will be positioned accordingly (OK/Cancel/Apply button will be aligned to the left side as well).
Example:
BEGIN_MESSAGE_MAP(...)
ON_WM_NCCREATE()
...
END_MESSAGE_MAP()
BOOL CMyPropertySheet::OnNcCreate(LPCREATESTRUCT pc)
{
BOOL res = CMFCPropertySheet::OnNcCreate(pc);
SetWindowLongPtr(m_hWnd, GWL_EXSTYLE,
WS_EX_LAYOUTRTL | GetWindowLongPtr(m_hWnd, GWL_EXSTYLE));
return res;
}
Alternatively, do this in OnInitDialog, use ::FindWindowEx(m_hWnd, 0, WC_TABCONTROL, 0) to find tab control's handle and change its style. This way the buttons are not re-positioned. Example:
BOOL CMyPropertySheet::OnInitDialog()
{
BOOL res = CMFCPropertySheet::OnInitDialog();
SetWindowLongPtr(m_hWnd, GWL_EXSTYLE,
WS_EX_LAYOUTRTL | GetWindowLongPtr(m_hWnd, GWL_EXSTYLE));
HWND htabctrl = ::FindWindowEx(m_hWnd, 0, WC_TABCONTROL, 0);
SetWindowLongPtr(htabctrl, GWL_EXSTYLE,
WS_EX_LAYOUTRTL | GetWindowLongPtr(htabctrl, GWL_EXSTYLE));
return res;
}
Side note:
You can also call SetProcessDefaultLayout(LAYOUT_RTL) at the start of the process (for example in CMyWinApp::InitInstance). Then change the layout depending on the result from GetProcessDefaultLayout. So you remember not to accidentally change the style for the Latin version...
Related
I have create tab
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
icc.dwICC = (DWORD)ICC_TAB_CLASSES;
InitCommonControlsEx(&icc);
icc.dwICC = (DWORD)ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icc);
hwndTab = CreateWindow(WC_TABCONTROL,L"",WS_CHILD|WS_VISIBLE|WS_DLGFRAME|WS_CLIPSIBLINGS
,10,10,780,271,hwnd,(HMENU)3,hInstance,NULL);
TCITEM tcitem; tcitem.mask = TCIF_TEXT;
tcitem.pszText = L"Tab1";
TabCtrl_InsertItem(hwndTab,0,&tcitem);
and put a static control into the tab
CreateWindow(L"STATIC",L"Static control 1",
WS_CHILD|WS_VISIBLE,50,30,65,24,hwndTab,(HMENU)NULL,hInstance,NULL);
But the Static control 1 have the grey background. How can I give it a color.
Thanks
Because you've made the static control a child of the tab it will be sending WM_CTLCOLORSTATIC messages to the tab control. If you sub-class the tab control you can catch this message and modify its colours. Note however that with visual styles enabled the tab control isn't a flat single colour - it's more of a gradient, so even returning a matching solid colour won't necessarily look that great.
Tab controls are not meant to be parents. For each tab page, you should create a new modeless dialog as a sibling to the tab and place your static control in it. If you obey the following rules:
reference Common Controls v6 in your manifest
call InitCommonControlsEx() on startup
no WS_CLIPCHILDREN in your main window
no handling of WM_ERASEBACKGROUND
call EnableThemeDialogTexture() in the WM_INITDIALOG handler of the modeless dialog
call SetWindowPos(tab, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE) after creating the tab page (or it will be on the wrong end of the Z order)
… then your static control will be drawn with the correct color gradient, as will all other controls (checkboxes, sliders, …) in the modeless dialog, on all Windows version from XP to 10.
We have a series of dialogs in our application for which the dialog template defines 4 buttons along the bottom of the screen. However (depending which version of our hardware the application is running on) we sometimes create 2 additional buttons and then line up the 6 buttons (4 from the template, 2 created by calling CButton::Create()) along the bottom.
The problem I have is that usually the user can move the focus between these buttons using the left / right arrow keys (there is no mouse or touchscreen, just a restricted keyboard). This follows the control TAB-order as you would expect for the 4 buttons from the template. However the 2 dynamically create controls appear to be inserted at the beginning of the TAB-order, and that means (because they are put at the right-hand side of the screen) that they are in the "wrong" order as far as the cursor keys goes. In other words, when the focus gets to the left-hand button (TAB order 1) pressing the left arrow jumps the focus to the button on the right-hand side, which is just plain confusing...
There appears to be some link between Z-order (which I can affect with SetWindowPos()) and TAB-order but it does not seem to be a simple 1-to-1: by changing the Z-order I can move the sequence around so that the buttons are completely in the wrong order, so I can change the Z-order, but I can't work out how to get them in the right order.
Can anyone give a concise explanation of how TAB-order works, and how to control the sequencing of controls at runtime?
Edit:
kol suggested below using SetWindowPos() to set the Z-order. This I had tried before but it does not let the cursor keys control the focus as expected.
However, by rigging this up so I can use TAB (as a test -- this isn't practical for the end-user solution) I find that kol's solution does resolve the TAB-order. The problem I have is that this is not the same as the order used by the cursor keys!
So, revised question: how can I specify the order in which left / right cursor keys move focus between controls?
Solution:
With assistance from kol and MarkRansom's input, I have got this working now.
I used SetWindowPos() as suggested by kol to put my new buttons after the existing buttons in the TAB order, and then (as Mark suggested) made the first button WS_GROUP | WS_TABSTOP but cleared those flags from the other buttons.
However this was not enough to solve the problem, with the 2 new buttons still appearing to come before the first rather than after the second when using arrow keys (not TAB) to move around.
I looked at my dialog template, and it was like this:
IDD_QUERY DIALOG 0, 0, 156, 34
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "+++Skey1+++",IDC_SKEY_1,1,21,36,12
PUSHBUTTON "+++Skey2+++",IDC_SKEY_2,40,21,37,12
PUSHBUTTON "+++Skey3+++",IDC_SKEY_3,79,21,36,12
PUSHBUTTON "+++Skey4+++",IDC_SKEY_4,118,21,36,12
LTEXT "Static",IDC_QUERY_MSG,2,1,153,15
END
So the static IDC_QUERY_MSG, which is used to display information to the user, was coming after the 4th button in the template. To resolve the problem, I moved IDC_QUERY_MSG before the first button (IDC_SKEY_1): this means the 6 buttons are not split up by a static inbetween, and has solved the problem.
Thanks to everyone for their assistance!
Use the SetWindowPos member of your buttons. Calling it on button A and setting its first parameter to button B, puts button A after button B in the TAB order. If you want to set the order of two controls, you have to know the controls before and after them in the TAB order - this example shows how to do this (its not MFC but pure WinAPI, but it's easy to understand).
== UPDATE ==
I created a dialog template with four buttons at the bottom and an editbox at the top, with TAB order button1 -> button2 -> button3 -> button4 -> editbox -> button1 -> ... In OnInitDialog, I added two additional buttons at runtime, and inserted them into the existing TAB order between button4 and editbox using SetWindowPos and GetNextWindow. Pressing TAB repeatedly shows that the TAB order is correct: button1 -> button2 -> button3 -> button4 -> button5 -> button6 -> editbox -> button1 -> ...
class CTestDlg : public CDialogEx
{
public:
CTestDlg() : CDialogEx(CTestDlg::IDD) {}
enum { IDD = IDD_TESTDIALOG };
protected:
CButton button5;
CButton button6;
virtual BOOL OnInitDialog();
};
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
CButton* button4 = (CButton*)GetDlgItem(IDBUTTON4);
CWnd* next = button4->GetNextWindow(GW_HWNDNEXT);
button5.Create("Button5", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(340, 172, 415, 195), this, 1005);
button5.SetWindowPos(button4, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
button6.Create("Button6", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(422, 172, 497, 195), this, 1006);
button6.SetWindowPos(&button5, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
if (next != NULL)
next->SetWindowPos(&button6, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
return TRUE;
}
void CDynamicMfcButtonTestApp::OnTestRun()
{
CTestDlg testDlg;
testDlg.DoModal();
}
== UPDATE2 ==
The order of controls can be set using SetWindowPos and GetNextWindow, as above. The "tab order" and the "arrow order" can be set by setting the WS_TABSTOP and WS_GROUP styles of the controls:
The WS_TABSTOP style specifies the controls to which the user can
move by pressing the TAB key or SHIFT+TAB keys.
The WS_GROUP style
marks the beginning of a group of controls. If a control in the group
has the input focus when the user begins pressing direction keys, the
focus remains in the group. In general, the first control in a group
must have the WS_GROUP style and all other controls in the group must
not have this style. All controls in the group must be
contiguous—that is, they must have been created consecutively with no
intervening controls.
More details can be found on MSDN, here.
Try this:
Create a vector of dlg ids and populate it in the order you want the tab order to be:
typedef std::vector<int> TABLIST;
TABLIST lst;
lst.push_back( IDC_CONTROL1 );
lst.push_back( IDC_CONTROL2 );
lst.push_back( IDC_CONTROL3 );
Then call the setTabOrder method with the list:
void setTabOrder( TABLIST * plstTabs)
{
// Iterate through the list and put each item at the top of the tab order.
// Remember to do this backwards so the last item is actually the first item
// in the tab order
HDWP hDefer = BeginDeferWindowPos( (int)plstTabs->size() );
for ( TABLIST::reverse_iterator itTab = plstTabs->rbegin(); itTab != plstTabs->rend(); ++itTab )
{
DeferWindowPos( hDefer, GetDlgItem( *itTab )->m_hWnd, HWND_TOP, 0,0,0,0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
}
EndDeferWindowPos( hDefer );
}
Modified version of #snowduded's code that makes sure the WS_TABSTOP style is in also.
// Build your order in a vector.
std::vector<WORD> vCtrlIds;
vCtrlIds.push_back(IDC_CONTROL1);
vCtrlIds.push_back(IDC_CONTROL2);
vCtrlIds.push_back(IDC_CONTROL3);
// ... keep doing it!
// Iterate through the list and put each item at the top of the tab order.
// Remember to do this backwards so the last item is actually the first item
// in the tab order.
HDWP vDefer = BeginDeferWindowPos(vCtrlIds.size());
for(auto vCtrlId(vCtrlIds.rbegin()); vCtrlId != vCtrlIds.rend(); ++vCtrlId){
HWND vCtrl(GetDlgItem(*vCtrlId)); // Grab the handle.
SetWindowLongPtr(vCtrl, GWL_STYLE, // Make sure we have WS_TABSTOP.
GetWindowLongPtr(vCtrl, GWL_STYLE) | WS_TABSTOP);
DeferWindowPos(vDefer, vCtrl, HWND_TOP, 0, 0, 0, 0, // Reorder.
SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
}
EndDeferWindowPos(vDefer);
Props to #snowdude for an excellent reusable solution.
If you are talking about a tab order of different controls over a dialog:
Try this
Open the dlg in resource and press Ctrl+D
Now you can set order by selecting controls one after another.
You can't change the tab order in runtime. What you can do is to put these two buttons in your dialog resource (with the right tab order), and make them invisible. Then you show/arrange the buttons as soon as you need them.
I have an MFC MDI application I've developed in Visual Studio with a tabbed interface. I would like to open views in the tab group that are non-document views – i.e. they have no associated document, no need to save them, etc. In a way they would behave like a non-modal dialog, but tabbed. [These windows are simply to display information and take commands]
The internal machinery of the MDI apps seems very geared toward working with the DocTemplate – Document – Frame – View object structures along with their associated windows.
Q1) Anybody got any ideas on how to create such windows and add them into the already-established MDI tab group? I’ve tried to create a RichEdit window and added it in, with:
// m_wndListingView will be a non-editable CRichEditCtrl
m_wndListingView->Create(WS_CHILD | WS_VISIBLE | ES_WANTRETURN | WS_VSCROLL |
WS_HSCROLL | ES_MULTILINE | ES_LEFT | ES_AUTOHSCROLL | ES_SAVESEL |ES_READONLY,
CRect(0, 0, 20, 20), pMainFrame, 1234);
// get Tab control and add a new tab
CMFCTabCtrl *mm_wndTabCtrl = &pMainFrame->GetMDITabs();
mm_wndTabCtrl->AddTab (m_wndListingView, _T("LISTING"));
This created and displayed the window .. but it was not added to the tab group.
Q2) If I managed to get a window (perhaps it needs to be a frame window) displayed properly in the tab group, how do I tell the ‘system’ that when the user closes it, I do not want the app to prompt the user to Save the document ? Perhaps I can overload an 'OnClose' method ... but it can't be document::OnClose(), because there is no document.
Thanks for any ideas,
CAS
You need to create a frame and view on which to host your rich edit. This can be done without a document. The view will be the parent of the richedit (rather than pMainFrame).
Something along these lines (warning, untested):
CFrame* pFrame = (Crame*)RUNTIME_CLASS( CFrame )->CreateObject();
CCreateContext context;
context.m_pNewViewClass = RUNTIME_CLASS( CView );
context.m_pCurrentDoc = NULL;
context.m_pCurrentFrame = NULL;
context.m_pLastView = NULL;
context.m_pNewDocTemplate = NULL;
// NOTE: create IDR_SOMERESOURCE string (for tab title), menu, etc as needed
BOOL frameLoaded = pFrame->LoadFrame( IDR_SOMERESOURCE, WS_OVERLAPPEDWINDOW, pMainFrame, &context );
if (frameLoaded)
Frame->InitialUpdateFrame( NULL, TRUE );
// now create your rich edit with the view as its parent
How to set tooltip at runtime in MFC Treeview ?
I am creating treeview like this :
m_pTreeview->Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP |
TVS_SINGLEEXPAND,CRect(38, 82, 220 ,250), this, IDC_NDS_TREEVIEW);
Any help is appreciated..
Here some code : -- In .H file
afx_msg void OnMyTreeGetInfoTip(NMHDR pNMHDR, LRESULT pResult);
In BEGIN MESSAGE MAP block add -
ON_NOTIFY_REFLECT (TVN_GETINFOTIP, OnMyTreeGetInfoTip)
And use handler
void CMyTreeView::OnMyTreeGetInfoTip(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTVGETINFOTIP pGetInfoTip = (LPNMTVGETINFOTIP)pNMHDR;
CString strItemTxt = m_TreeCtrl.GetItemText(pGetInfoTip->hItem);
strcpy(pGetInfoTip->pszText, strItemTxt);
*pResult = 0;
}
If you're referring to the tooltips for items in the tree control, you need to add TVS_INFOTIP to the window styles in Create (see list of tree-view styles). You'll also have to handle the TVN_GETINFOTIP notification message to provide the tooltip text depending on the item.
Use TVS_INFOTIP style to tree-view, and handle the TVN_GETINFOTIP notification using an ON_NOTIFY handler. Typecast the NMHDR ptr to NMTVGETINFOTOOLTIP ptr as
(NMTVGETINFOTOOLTIP *)pnmhdr and then set the tooltip string in this structure.
Pankaj's answer works if you are deriving your own control from CTreeControl.
Cassablanca's answer is correct only the code is missing.
So here are some tips from my own experience.
If you are not creating the control explicitly the GETINFOTIP style can be specified in the resource file where the control is being defined.
otherwise the style can be modified at runtime by getting the tree's window handle
HWND htreectrl = m_TreeCtrl.GetSafeHwnd();
LONG nOldStyle = GetWindowLong( htreectrl, GWL_STYLE);
LONG nNewStyle = nOldStyle & TVS_INFOTIP;
SetWindowLong( htreectrl, GWL_STYLE, nNewStyle);
To be able to handle GETINFOTIP:
If you are using the TreeControl as a member control inside a dialog:
ON_NOTIFY (TVN_GETINFOTIP, IDC_TREE, OnMyTreeGetInfoTip)
Else if you are deriving your own control from CTreeControl then use this:
ON_NOTIFY_REFLECT(TVN_GETINFOTIP, OnMyTreeGetInfoTip)
Hope this helps someone.
I made a custom control derived from CWnd (a line chart) and I'm wondering if I can use CToolTipCtrl for displaying tool tips for points on the graph. If yes, how could I do that?
Btw, when I move my mouse over the point, the rectangle containg string with information about values of the point, should pop up.
Yes, this works, actually I do this exact same thing, also in a line graph chart, however there are a few drawbacks/remarks. The message handling is a bit wonky, with some messages not being send according to the documentation, and some workarounds being necessary to keep the control self-contained (not requiring help from the parent to reflect notifications).
What you do is declare a variable in your CWnd-derived class
CToolTipCtrl m_ToolTipCtrl;
CString m_ToolTipContent;
Then do this on OnCreate:
m_ToolTipCtrl.Create(this, TTS_ALWAYSTIP);
m_ToolTipCtrl.Activate(TRUE);
Optionally, you can also set the delay time:
m_ToolTipCtrl.SetDelayTime(TTDT_AUTOPOP, -1);
m_ToolTipCtrl.SetDelayTime(TTDT_INITIAL, 0);
m_ToolTipCtrl.SetDelayTime(TTDT_RESHOW, 0);
When you want to show your tooltip (presumably in OnMouseMove()), use
m_ToolTipCtrl.Pop();
BUT this only works in UNICODE builds. So if you're still on MBCS (like I am), you can only show the tooltip after a certain delay.
Use this to set your tooltip text (also in OnMouseMove):
// Not using CToolTipCtrl::AddTool() because
// it redirects the messages to the parent
TOOLINFO ti = {0};
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND; // Indicate that uId is handle to a control
ti.uId = (UINT_PTR)m_hWnd; // Handle to the control
ti.hwnd = m_hWnd; // Handle to window
// to receive the tooltip-messages
ti.hinst = ::AfxGetInstanceHandle();
ti.lpszText = LPSTR_TEXTCALLBACK;
ti.rect = <rectangle where, when the mouse is over it, the tooltip should be shown>;
m_ToolTipCtrl.SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
m_ToolTipCtrl.Activate(TRUE);
m_ToolTipContent = "my tooltip content";
Furthermore, you need to handle TTNNeedText:
// The build-agnostic one doesn't work for some reason.
ON_NOTIFY_EX(TTN_NEEDTEXTA, 0, OnTTNNeedText)
ON_NOTIFY_EX(TTN_NEEDTEXTW, 0, OnTTNNeedText)
BOOL GraphCtrlOnTTNNeedText(UINT id, NMHDR* pTTTStruct, LRESULT* pResult)
{
TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pTTTStruct;
//pTTT->lpszText = "some test text";
//pTTT->lpszText = m_ToolTipContent;
strncpy_s(pTTT->lpszText, 80, m_ToolTipContent, _TRUNCATE);
return TRUE;
}
You'll have to modify this a bit, and read the documentation of the functions and messages, to get this to work in your project but yes it can be done.
For those that may still be looking for an answer to this as I was. (I couldn't get the one above to work - Things in MFC may have changed.)
All code is contained within the custom control class.
In your class definition add:
CToolTipCtrl ToolTip;
In PreSubclassWindow() add:
#define TOOLTIP_ID 1
ToolTip.Create(this, TTS_ALWAYSTIP );
CRect rc;
GetClientRect(rc);
ToolTip.AddTool(this, "Tool tip text", rc, TOOLTIP_ID);
In PreTranslateMessage() add:
ToolTip.RelayEvent(msg);
Whenever your tool tip text changes add:
ToolTip.UpdateTipText("New Tip Text", this, TOOLTIP_ID);