MFC MDI Designing user preferences dialog GUI layout functionality - c++

In this instance I am not "stuck" but looking for some 'guidance' on how to perform a GUI layout mechanism that makes sense and easy to construct. I am using a TreeControl that visually "matches" Winamp's options/preferences dialog for all the settings and program options.
TO BE CLEAR: This is not something I've ever attempted and looking for user input to help me get across the finish line.
I have 6 items in the 'tree' (the 'parent') and 5 'subitems'. I have it fully constructed and I can get the data from whatever node is selected (which I'll post below images).
I am using a "do.Modal dialog" and, when I click on the tree node, I want the right side of the dialog to update to the needed controls for that node function. The execution of controls through "show" and "hide" to me seems pretty easy. The issue I have is the how to do the visual "static" controls in the resource editor when each "node paged" controls may or may not sit on top of each other visually during design time. During run time, when each node is selected on the nodes controls will be active but during design time, I may have controls sitting on top of each other and will be a logistical nightmare trying to sort out their positions etc.
How do I resolve that aspect of building it? I think of it as a select: 'node' show: 'option page controls' so I get the logic; I'm just wondering if I should have separate "popup" pages and call those … or deal with the controls directly. Does that makes sense?
I've done some searching on how to do this, but the examples are all over the place and I figured asking here makes the most sense. I'm sure there are multiple ways to do this, I'm just looking for the shortest path and easiest to maintain and possibly expand on the options in the future.
The examples of the dialog and the source code I'm using:
Here is the initialization code:
BOOL CSettingsDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
HTREEITEM hParent, hToolbars, hColorScheme, hTabStyles, hPowerUser,
hResetApp;
//HTREEITEM hCompany, hLevel, hLevelNext; // Used in core example, not needed here
hParent = m_TreeCtrl.InsertItem((L"Preferences"), TVI_ROOT);
hToolbars = m_TreeCtrl.InsertItem((L"Toolbars"), hParent);
hColorScheme = m_TreeCtrl.InsertItem((L"Color Scheme"), hParent);
hTabStyles = m_TreeCtrl.InsertItem((L"Tab Styles"), hParent);
hPowerUser = m_TreeCtrl.InsertItem((L"Power User"), hParent);
hResetApp= m_TreeCtrl.InsertItem((L"Reset All Options"), hParent);
m_TreeCtrl.Expand(hParent, TVE_EXPAND);
m_TreeCtrl.SelectItem(hParent);
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Preferences Settings"));
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Here is the dialog framework that I'm starting from scratch with just to arrive here:
void CSettingsDlg::OnTvnSelchangedTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
//LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
//SetDlgItemInt(IDC_TXT_TREE1, m_TreeCtrl.GetItemData(pNMTreeView->itemNew.hItem));
//*pResult = 0;
HTREEITEM sel = m_TreeCtrl.GetSelectedItem();
CPoint point;
CString str;
GetCursorPos(&point);
m_TreeCtrl.ScreenToClient(&point);
UINT flags = 0;
HTREEITEM hitItem = m_TreeCtrl.HitTest(point, &flags);
if (hitItem && sel != hitItem)
{
sel = hitItem;
m_TreeCtrl.SelectItem(sel);
str = m_TreeCtrl.GetItemText(sel);
//MessageBox((LPCTSTR)str); // Just to verify.
}
if (str == "Preferences")
{
this->SetWindowText(_T("Preferences Settings"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Preferences Settings"));
}
if (str == "Toolbars")
{
this->SetWindowText(_T("Toolbars Settings"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Toolbars Settings"));
}
if (str == "Color Scheme")
{
this->SetWindowText(_T("Color Scheme"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Color Scheme Settings"));
}
if (str == "Tab Styles")
{
this->SetWindowText(_T("Tab Styles Settings"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Tab Styles Settings"));
}
if (str == "Power User")
{
this->SetWindowText(_T("Power User Settings"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Power User Settings"));
}
if (str == "Reset All Options")
{
this->SetWindowText(_T("Reset All Settings"));
GetDlgItem(IDC_SETTINGS_CAPTION)->SetWindowText(_T("Reset All Settings"));
}
*pResult = 0;
}
So as shown, you can see that I have the ability to change the static caption to the "active" page node that is selected from the tree. I'm looking to solve the best way to handle the GUI elements that will go on the right side....
Some controls for example will be:
Color scheme Node: 5 check boxes for theme changing
Toolbars Node: Radio button for Docked or Floating
Power User Node: Checkbox to disable user warning within program operation
Tab Styles Node: Radio buttons to select flat, 3d, whatever.
and when the user "selects" whatever it writes it to the registry and then I call the function to read the data blah blah blah...
So that is where I'm at. Does anyone have any suggestions on a course of action, so I don't paint myself into a corner?

Although I can't actually speak for how the authors of the "Winamp" software implemented their UI, I would suggest that you consider using a property sheet for your case, where each set of controls can be implemented as separate property pages, each of which will be defined (both in the resource script and in the C++ code) in ways very similar to individual modal dialog boxes.
The 'classic' property sheet has a horizontal row of tabs (normally, at the top of the sheet) to select each page, as shown in the sample below. Indeed, some sources/platforms refer to a property sheet as a "tabbed dialog".
However, the new(ish) CMFCPropertySheet class allows you to replace that tab control with one of several other options, which will show as separate 'panes' (on the left-hand side of the sheet). To use one of these alternative styles, call the SetLook() member function in your constructor override with the CMFCPropertySheet::PropSheetLook_Tree enum value. The example shown in the link is not exactly what you have shown in your question, but the CMFCPropertySheet and CMFCPropertyPage classes allow many, many customizations to their appearance and style.
I have never actually used the PropSheetLook_Tree style, but the image below shows "one I made earlier" with the PropSheetLook_OutlookBar style.

Related

MDI Tab names changing back... / Taskbar Preview not selecting

I'm creating an MDI application, WITHOUT the Document/View.
I am also using MDITabbedGroups.
I had a problem, because I have to name may tabs dynamically (names are in the DB and user created), meaning I couldn't use resources for the names.
I create all the tabs as 'Program name', then rename them by calling the following after create:
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_COMMAND(ID_SET_COLOR, CCOnSetColor)
END_MESSAGE_MAP()
PostMessage(WM_COMMAND, (WPARAM)ID_SET_COLOR);
void CChildView::CCOnSetColor()
{
CMDIChildWndEx* pFrame = (CMDIChildWndEx*)GetParent();
CMFCTabCtrl* pTabCtrl = pFrame->GetRelatedTabGroup();
if (pTabCtrl != NULL)
{
pTabCtrl->SetTabBkColor(pTabCtrl->GetActiveTab(), GetTabCol());
pTabCtrl->SetTabLabel(pTabCtrl->GetActiveTab(), GetTabName());
pTabCtrl->RedrawWindow();
}
}
But the problem was that when I switched tabs the names would change back to 'Program Name'.
I managed to avoid this by skipping over CMDIChildWndEx in the OnMDIActivate call as so:
void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* activated, CWnd* disbled)
{
CWnd::OnMDIActivate(bActivate, activated, disbled);
}
I know, I know, bad. However:
This worked fine, all other functionality seemed to be working, tabs were switching, active window was correct etc etc...
Until I noticed that on mouse over of the task bar, I got a preview of all open tabs, and if I clicked on one of the inactive tabs things went wrong.
Now the active frame / view (GetActiveFrame() / GetActiveView() in MainFrame) was the one I click on, but it is not drawn and the tab not switched to.
So I tried:
void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* activated, CWnd* disbled)
{
CMDIChildWndEx::OnMDIActivate(bActivate, activated, disbled);
CMFCTabCtrl* pTabCtrl = GetRelatedTabGroup();
if (pTabCtrl != NULL)
{
pTabCtrl->SetTabBkColor(pTabCtrl->GetActiveTab(), m_wndView.GetTabCol());
pTabCtrl->SetTabLabel(pTabCtrl->GetActiveTab(), m_wndView.GetTabName());
// pTabCtrl->SetTabIcon(pTabCtrl->GetActiveTab(), 2);
pTabCtrl->RedrawWindow();
}
}
&&&
void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* activated, CWnd* disbled)
{
CMDIChildWndEx::OnMDIActivate(bActivate, activated, disbled);
m_wndView.PostMessage(WM_COMMAND, (WPARAM)ID_SET_COLOR);
if(disbled) ((CChildFrame*)disbled)->m_wndView.PostMessage(WM_COMMAND, (WPARAM)ID_SET_COLOR);
}
Now the tab switches correctly, but only the tab being activated gets the correct name, all inactive tabs go back to 'Program Name'
interestingly the color remains correct.
So the Question:
Can I permanently change the name some how / where.
I saw in MS Documentation that it can be set in CDocument, but I don't have a Document...
Alternatively what call / calls do I need to catch to set my name every time?
Also Bonus Q:
Can I make the Preview show only the 'Active' Tab, and not the others?
After much hunting around in the AFX source files, I found this:
CString CMDIChildWndEx::GetFrameText() const
{
ASSERT_VALID(this);
CString strText;
GetWindowText(strText);
return strText;
}
& this
void CMDIFrameWnd::OnUpdateFrameTitle(BOOL bAddToTitle)
{
if ((GetStyle() & FWS_ADDTOTITLE) == 0)
return; // leave it alone!
... // rest of func
}
so it is as simple as removing the style flag the MS puts in automatically (via wizard create project), on the func BOOL PreCreateWindow(CREATESTRUCT& cs).
Then setting the title of the Frame via SetWindowText(title);

Split view in two with a menu button? (MFC)

I have an MFC program written that reads files, stores the data, and draws it as text on the client view.
I want to make a menu button View->Split that splits the client area into two, separately scrolling views displaying the same data.
I saw some things about CWndSplitter online and read through some documentation but none of it has proved to be useful because they talk about using OnCreate and deleting the default view to get it to work. This is not an option. I want to keep the default view, but split it in two if the user clicks the button.
I've currently created a CWndSplitter member variable and defined a menu button event handler in my SDI-1View.cpp. When called, it does absolutely nothing but cause the screen to flicker and a second click crashes the program.
void CSDI1View::OnViewSplit32778()
{
// TODO: Add your command handler code here
/*
int rows = 2;
int columns = 1;
if (!m_wndSplitter.CreateStatic(this, rows, columns))
return;
if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CSDI1View), CSize(100, 100), NULL) ||
(!m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(CSDI1View), CSize(100, 100), NULL)))
{
m_wndSplitter.DestroyWindow();
return;
}
*/
}
Can anyone tell me what the normal approach to splitting a client view in half is? I just want to integrate that into an event handler.
Any help would be greatly appreciated.
Thanks.
--------------------------------EDIT----------------------------------
I now have the following code in my Split button event handler, thanks to the outline provided by xMRi, but it is still not working properly...
void CMainFrame::OnViewSplit()
{
// TODO: Add your command handler code here
//calculate client size
CRect cr;
GetClientRect(&cr);
if (!m_mainSplitter.CreateStatic(this, 2, 1))
{
MessageBox(_T("Error setting up splitter frames! (CreateStatic)"),
_T("Init Error!"), MB_OK | MB_ICONERROR);
return;
}
// Set the parent of the splitter window to the current view
CSDI1View * view = CSDI1View::GetView();
m_mainSplitter.SetParent(this);
view->SetParent(&m_mainSplitter);
// Create a CCreateContext
CCreateContext cc;
CRuntimeClass* prt = RUNTIME_CLASS(CSDI1View);
cc.m_pNewViewClass = prt;
cc.m_pCurrentDoc = view->GetDocument();
cc.m_pNewDocTemplate = NULL;
cc.m_pLastView = NULL;
cc.m_pCurrentFrame = this;
if (!m_mainSplitter.CreateView(0, 0,
cc.m_pNewViewClass,
CSize(cr.Width(), cr.Height()/2), &cc))
{
MessageBox(_T("Error setting up splitter frames! (CreateView 1)"),
_T("Init Error!"), MB_OK | MB_ICONERROR);
return;
}
if (!m_mainSplitter.CreateView(1, 0,
cc.m_pNewViewClass,
CSize(cr.Width(), cr.Height()/2), &cc))
{
MessageBox(_T("Error setting up splitter frames! (CreateView 2)"),
_T("Init Error!"), MB_OK | MB_ICONERROR);
return;
}
m_bInitSplitter = TRUE;
}
Upon clicking the view->split button, I get a "Debug Assertion Error" popup and the first call to CreateView returns FALSE, displaying my messagebox: "Error setting up splitter frames! (CreateView 1)"
A static splitter is for a static split--i.e., a window that's always split. You usually use it when you want to have a different view in each pane of the split (e.g. display a column of numbers in one pane, and a graph in the other pane).
For a situation like yours that you want to be able to have the window, then later split it and have two essentially identical views, you (at least normally) want to use a dynamic splitter.
At least normally, you create the splitter when you create the view. That will create a window that has a handle at the top, right-hand corner that the user pulls down to split the view:
To split the window, the user pulls down on the handle:
When the split is where they want it, they release the mouse button, and the view splits into two separately scrollable sections:
Since you didn't specify whether you wanted vertical or horizontal splitting, I set this one up to allow either or both:
The code for this looks something like this:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext) {
return my_splitter.Create(this,
2, 2, // 2 rows, 2 columns
CSize(20, 20), // minimum pane size
pContext);
}
where my_splitter is defined something like this:
CSplitterWnd my_splitter;
If you want to use a splitter window on demand, you need to change the parent of the current view.
So the steps are:
create a simple SDI application
On demand create a splitter window
Use SetParent to the current view and set it as a parent in the splitter window.
Create another window in the second splitter.
And the way back.
Use SetParent and attach the normal view back to the main frame.
Destroy the splitter
There are active samples how to switch a view in a current document (MSDN). It helps you how IDs must be replaced and changed.

How to highlight the selected item of long list selector in windows phone 8

I've developed Windows Phone 8 application. In that I'm using the Long list selector to display items in the list. Everything is fine up till now. When the user clicks on any item of long list selector, I want to highlight that item's background color, so that the user clearly understands that he/she has selected an item.
Could you please tell me how to do this in windows phone 8.
I'm looking forward for the response.
http://code.msdn.microsoft.com/wpapps/Highlight-a-selected-item-30ced444
Detailed example of how to do it
I like to have more control of my application through code and avoid doing things in the xaml where it can get complicated. Below is a simpler way I feel and gives more control in code and require minimal changes in the xaml. It keeps the xaml nice and clean for what should be a really simple action.
Add a "BackColor" (or other string) property to your bound object
public string BackColor { get; set; }
Bind that property to something in your xaml like the background or a stack panel or the border color of a border, something that will present the visual change. E.g.
<StackPanel Orientation="Horizontal" Background="{Binding BackColor}">
In your long list selector code "SelectionChanged" event update the bound objects using the AddedItems and RemovedItems collections from SelectionChangedEventArgs e
if (e.AddedItems.Count > 0)
{
if (e.AddedItems[0] != null)
{
oMyObject = (MyServices.MyObjectDao)e.AddedItems[0];
oMyObject.BackColor = "Red";
}
}
if (e.RemovedItems.Count > 0)
{
if (e.RemovedItems[0] != null)
{
oMyObject = (MySercvices.MyObjectDao)e.RemovedItems[0];
oMyObject.BackColor = "Black";
}
}
You can use simple colors like in the example, or you can use any predefined colors from your xaml

MFC tooltips only show up on special occasions

I have been tasked with assigning tooltips to each item in a configuration menu. I have completed "adding" the tooltip to each control on the page, but it seems sometimes the tooltip shows up and sometimes it does not, depending on the position of the control on the screen.
To tooltip-erize the pages I first
EnableToolTips(TRUE);
In each CPropertyPage's OnInitDialog method.
I then add the notification map
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipText)
With the function OnToolTipText looking as such
BOOL CCfgPrefPage::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
UINT nID = pNMHDR->idFrom;
if (pTTT->uFlags & TTF_IDISHWND)
{
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
if( nID == GetDlgItem(IDC_PICKDIST_EDIT)->GetDlgCtrlID())
_tcsncpy_s(pTTT->szText, _T("Tool Tip Text"), _TRUNCATE);
else if( nID == GetDlgItem(IDC_ENDPTTOL_EDIT)->GetDlgCtrlID())
_tcsncpy_s(pTTT->szText, _T("Tool Tip Text"), _TRUNCATE);
pTTT->lpszText = pTTT->szText; // Sanity Check
pTTT->hinst = AfxGetResourceHandle(); // Don't think this is needed at all
return TRUE;
}
}
return FALSE;
}
It seems for some of my controls the tool tip will not show up. For most of the check box controls the tool tip displays, but for a few they just do not show. There are no other controls covering them up, they are not disabled.
Another thing, if I use the non-standard cursor windows repeatedly flashes the tool tip, so much so it is unreadable in some cases. How can I fix this? This is not a problem on CEdit controls, so why is it a problem elsewhere?
EDIT: Update, the controls that have been on the pages for years seem to show tool tips. Any control that I try to add now/today will not show tool tips at all. No matter the position, control type, settings, I cannot get a single tool tip to show on a newly inserted control.
If you do not want to use helper class I have proposed then fix the problems in your code.
First, use ON_NOTIFY_EX_RANGE macro when mapping the even handler, like this (this will cover all IDs):
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
Next, you need to fix your function. I see a few problems here. First, when testing for TTF_IDISHWND flag you only need to re-initalise the nID. You do not need to apply this to the whole function. Second, after all manipulations, your nID will be the actual dialog ID. There is no need to GetDlgItem() function
BOOL CCfgPrefPage::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
UINT nID = pNMHDR->idFrom;
if (pTTT->uFlags & TTF_IDISHWND)
{
nID = ::GetDlgCtrlID((HWND)nID);
}
if(nID)
{
if( nID == IDC_PICKDIST_EDIT)
_tcsncpy_s(pTTT->szText, _T("Tool Tip Text"), _TRUNCATE);
else if( nID == IDC_ENDPTTOL_EDIT)
_tcsncpy_s(pTTT->szText, _T("Tool Tip Text"), _TRUNCATE);
//pTTT->lpszText = pTTT->szText; // Sanity Check
*pResult = 0;
return TRUE;
}
return FALSE;
}
Working with a toolbar which repeats some menu items from menus of an older MFC application, I have worked on this issue of tool tips as well as (1) modifying the toolbar bit map to include additional icons and (2) providing user feedback on current application state. My problem is that I have to do most of this by hand rather than using the various wizards and tools.
What I have done is to (1) add new members to the CView derived class to handle additional messages, (2) modified the toolbar bit map to add in the additional icons using both MS Paint and the Resource Editor, and (3) added new event ids and event handlers to the message map for the CView derived class.
One problem I ran into with the toolbar bitmap change was that since I was inserting an icon, I had to shift an existing icon in the bitmap to the right. My first attempt at this resulted in the shifted icon showing as a blank on the application toolbar. Then I realized that I needed to add a bit more to the length of the toolbar bitmap. After adding a couple more columns to the last icon in the toolbar bitmap to make it a standard width in pixels, the icon displayed properly.
For tooltips I added the following to the message map:
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipText)
I then added the following method to my class to handle the notifications for my menu items. As a side note, it appears that OnToolTipText() is the standard method used in CFrameWnd class and CMDIChildWnd class however CView derives from CWnd as does CFrameWnd so I doubt it makes a difference as to what the method is named.
inline BOOL CPCSampleView::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
static wchar_t toolTextToggleExportSylk [64] = L"Toggle SYLK export.";
static wchar_t toolTextClearWindow [64] = L"Clear the log displayed.";
static wchar_t toolTextConnectLan [64] = L"Log on the POS terminal through the LAN.";
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
switch (pNMHDR->idFrom) {
case ID_TOGGLE_SYLK_EXPORT:
pTTT->lpszText = toolTextToggleExportSylk;
return TRUE;
case ID_WINDOW_CLEAR:
pTTT->lpszText = toolTextClearWindow;
return TRUE;
case ID_CONNECT_LAN_ON:
pTTT->lpszText = toolTextConnectLan;
return TRUE;
}
// if we do not handle the message then return FALSE to let someone else do it.
return FALSE;
}
For the user feedback on a menu item which toggles a file export when doing reports I provided the following changes to the message map and then implemented the necessary methods. There are two types of messages involved so I had to add two methods and two new message map entries:
// New message map entries to handle the menu item selection event
// and to update the menu item and the toolbar icon with state changes
ON_COMMAND(ID_TOGGLE_SYLK_EXPORT, OnToggleExportSylk)
ON_UPDATE_COMMAND_UI(ID_TOGGLE_SYLK_EXPORT, OnUpdateToggleExportSylk)
// New methods added to the CView derived class
// handle the menu selection event generated by either selecting the menu item
// from the menu or by clicking on the icon in the toolbar.
void CPCSampleView::OnToggleExportSylk()
{
// Exclusive Or to toggle the indicator bit from 0 to 1 and 1 to 0.
GetDocument()->ulReportOptionsMap ^= CPCSampleDoc::ulReportOptionsExportSylk;
}
// handle the request from the MFC framework to update the displayed state this
// not only does a check mark against the menu item it also causes the toolbar
// icon to appear depressed if click is set or non-depressed if click is not set
inline void CPCSampleView::OnUpdateToggleExportSylk (CCmdUI* pCmdUI)
{
if (GetDocument()->ulReportOptionsMap & CPCSampleDoc::ulReportOptionsExportSylk)
{
// SYLK export is turned on so indicate status to the user. This will
// put a check mark beside the menu item and show the toolbar button depressed
pCmdUI->SetCheck (1);
}
else
{
// SYLK export is turned off so indicate status to the user. This will
// remove the check mark beside the menu item and show the toolbar button as raised.
pCmdUI->SetCheck (0);
}
}
The resource file changes were needed to provide a new button for the toggle action as well as to add a new menu item for the toggle action. I am using the same resource id for several different things since these are all separate. So the id for the resource string is the same as for the menu item and is same for the toolbar button so as to simplify my life and make it easy to find all the particular bits and pieces.
The toolbar resource file definition looks like:
IDR_MAINFRAME TOOLBAR 16, 15
BEGIN
BUTTON ID_CONNECT_LAN_ON
SEPARATOR
BUTTON ID_WINDOW_CLEAR
SEPARATOR
BUTTON ID_TOGGLE_SYLK_EXPORT
SEPARATOR
BUTTON ID_APP_ABOUT
END
And the specific part of the menu, which uses the same resource id for the toggle event id looks like:
MENUITEM "Export to SYLK file", ID_TOGGLE_SYLK_EXPORT
Then to provide the status bar text which shows up with a mouse over there is a string table addition:
ID_TOGGLE_SYLK_EXPORT "Toggle export of SYLK format report files for spreadsheets."
The lpszText member of the struct is describe in the MSDN documentation for the TOOLINFO struct as:
Pointer to the buffer that contains the text for the tool, or
identifier of the string resource that contains the text. This member
is sometimes used to return values. If you need to examine the
returned value, must point to a valid buffer of sufficient size.
Otherwise, it can be set to NULL. If lpszText is set to
LPSTR_TEXTCALLBACK, the control sends the TTN_GETDISPINFO notification
code to the owner window to retrieve the text.
Reviewing the existing answer to this question, I wondered about the if statement check for the TTF_IDISHWND flag. The MSDN documentation for the TOOLINFO struct has this to say:
Indicates that the uId member is the window handle to the tool. If
this flag is not set, uId is the tool's identifier.

Tool tips for custom made control using CToolTipCtrl ? (MFC)

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);