MFC C++ Derive from CEdit and derive GetWindowText - c++

I am deriving from CEdit, to make a custom control. It would be nice, if like the MFC Feature Pack controls (Mask, Browsable) that I could change GetWindowText to actually report back not what is normally displayed on the control (for example, convert the data between hex and decimal, then return back that string).
Is it this possible in a derived CEdit?

Add message map entries for WM_GETTEXT and WM_GETTEXTLENGTH to your derived CEdit class:
BEGIN_MESSAGE_MAP( CMyEdit, CEdit )
ON_WM_GETTEXT()
ON_WM_GETTEXTLENGTH()
END_MESSAGE_MAP()
As we are overriding these messages we need a method of getting the original text of the edit control without going into endless recursion. For this we can directly call the default window procedure which is named DefWindowProc:
CStringW CMyEdit::GetTextInternal()
{
CStringW text;
LRESULT len = DefWindowProcW( WM_GETTEXTLENGTH, 0, 0 );
if( len > 0 )
{
// WPARAM = len + 1 because the length must include the null terminator.
len = DefWindowProcW( WM_GETTEXT, len + 1, reinterpret_cast<LPARAM>( text.GetBuffer( len ) ) );
text.ReleaseBuffer( len );
}
return text;
}
The following method gets the original window text and transforms it. Anything would be possible here, including the example of converting between hex and dec. For simplicity I just enclose the text in dashes.
CStringW CMyEdit::GetTransformedText()
{
CStringW text = GetTextInternal();
return L"--" + text + L"--";
}
Now comes the actual handler for WM_GETTEXT which copies the transformed text to the output buffer.
int CMyEdit::OnGetText( int cchDest, LPWSTR pDest )
{
// Sanity checks
if( cchDest <= 0 || ! pDest )
return 0;
CStringW text = GetTransformedText();
// Using StringCchCopyExW() to make sure that we don't write outside of the bounds of the pDest buffer.
// cchDest defines the maximum number of characters to be copied, including the terminating null character.
LPWSTR pDestEnd = nullptr;
HRESULT hr = StringCchCopyExW( pDest, cchDest, text.GetString(), &pDestEnd, nullptr, 0 );
// If our text is greater in length than cchDest - 1, the function will truncate the text and
// return STRSAFE_E_INSUFFICIENT_BUFFER.
if( SUCCEEDED( hr ) || hr == STRSAFE_E_INSUFFICIENT_BUFFER )
{
// The return value is the number of characters copied, not including the terminating null character.
return pDestEnd - pDest;
}
return 0;
}
The handler for WM_GETTEXTLENGTH is self-explanatory:
UINT CMyEdit::OnGetTextLength()
{
return GetTransformedText().GetLength();
}

Thanks to everyone for pointing me in the right direction. I tried OnGetText, but the problem seemed to be I couldn't get the underlying string or it would crash when calling GetWindowText (or just called OnGetText again...and couldn't find the underlying string).
After seeing what they did on masked control, I did a simpler answer like this. Are there any drawbacks? It seemed to not cause any issues or side effects...
Derive directly from GetWindowText
void CConvertibleEdit::GetWindowText(CString& strString) const
{
CEdit::GetWindowText(strString);
ConvertibleDataType targetDataType;
if (currentDataType == inputType)
{
}
else
{
strString = ConvertEditType(strString, currentDataType, inputType);
}
}

Related

Printing different documents silently in C++

I have folder of different documents like: pdf, txt, rtf, images.
My case is to send all documents to the printer (print it). Used framework is MFC and WinAPI. Current implementation has dialog box for choose documents and another dialog for choose printer.
Then question appears, how to print it all? Do I need to convert every documents to PDF, then merge it and print one pdf document? I will appreciate any advice in that field.
void CMultipleDocsPrintTestDlg::OnBnClickedButton1()
{
TCHAR strFilter[] = { _T("Rule Profile (*.pdf)||") };
// Create buffer for file names.
const DWORD numberOfFileNames = 100;
const DWORD fileNameMaxLength = MAX_PATH + 1;
const DWORD bufferSize = (numberOfFileNames * fileNameMaxLength) + 1;
CFileDialog fileDlg(TRUE, _T("pdf"), NULL, OFN_ALLOWMULTISELECT, strFilter);
TCHAR* filenamesBuffer = new TCHAR[bufferSize];
// Initialize beginning and end of buffer.
filenamesBuffer[0] = NULL;
filenamesBuffer[bufferSize - 1] = NULL;
// Attach buffer to OPENFILENAME member.
fileDlg.GetOFN().lpstrFile = filenamesBuffer;
fileDlg.GetOFN().nMaxFile = bufferSize;
// Create array for file names.
CString fileNameArray[numberOfFileNames];
if (fileDlg.DoModal() == IDOK)
{
// Retrieve file name(s).
POSITION fileNamesPosition = fileDlg.GetStartPosition();
int iCtr = 0;
while (fileNamesPosition != NULL)
{
fileNameArray[iCtr++] = fileDlg.GetNextPathName(fileNamesPosition);
}
}
// Release file names buffer.
delete[] filenamesBuffer;
CPrintDialog dlg(FALSE);
dlg.m_pd.Flags |= PD_PRINTSETUP;
CString printerName;
if (dlg.DoModal() == IDOK)
{
printerName = dlg.GetDeviceName();
}
// What next ???
}
You could make use of ShellExecute to do this. The parameter lpOperation can be set to print. To quote:
Prints the file specified by lpFile. If lpFile is not a document file, the function fails.
As mentioned in a similar discussion here on StackOverflow (ShellExecute, "Print") you should keep in mind:
You need to make sure that the machine's associations are configured to handle the print verb.
You referred to pdf, txt, rtf, images which should all be supported I would think by this mechanism.
ShellExecute(NULL, "print", fileNameArray[0], nullptr, nullptr, SW_SHOWNORMAL);
The last parameter might have to be changed (SW_SHOWNORMAL). This code would be put in a loop so you could call it for each file. And note that the above code snippet has not been tested.

How to get the current Tab Item name from CTabCtrl in MFC?

I am trying to get the text of the currently chosen tab in CTabCtrl.
int tabCurSel = currentTabCtrl->GetCurSel();
TCITEM tcItem;
tcItem.mask = TCIF_TEXT;
tcItem.cchTextMax = 256; //Do I need this?
CString tabCurrentCString;
currentTabCtrl->GetItem(tabCurSel, &tcItem);
tabCurrentCString = tcItem.pszText;
CT2A tabCurrentChar(tabCurrentCString);
std::string tabCurrentStr(tabCurrentChar);
return tabCurrentStr;
I clearly have some unnecessary string conversions and currently this returns a "Error reading characters of the string" in
tcItem.pszText;
How can I get the string from the CTabCtrl? I ultimately am trying to get an std::string but the main question is how to get the text from the tab.
tcItem.pszText is pointing to 0. To fill it with text, it has to point to a buffer before a call is made to GetItem:
Documentation for: CTabCtrl::GetItem
pszText
Pointer to a null-terminated string containing the tab text if the
structure contains information about a tab. If the structure is
receiving information, this member specifies the address of the buffer
that receives the tab text.
Example:
TCITEM tcItem { 0 };
tcItem.mask = TCIF_TEXT;
const int len = 256;
tcItem.cchTextMax = len;
TCHAR buf[len] = { 0 };
tcItem.pszText = buf;
currentTabCtrl->GetItem(tabCurSel, &tcItem);
Both tcItem.pszText and buf will point to the same text. Or use CString with CString::GetBuffer()/CString::ReleaseBuffer()
CString tabCurrentCString;
TCITEM tcItem;
tcItem.mask = TCIF_TEXT;
tcItem.cchTextMax = 256;
tcItem.pszText = tabCurrentCString.GetBuffer(tcItem.cchTextMax);
BOOL result = currentTabCtrl->GetItem(tabCurSel, &tcItem);
tabCurrentCString.ReleaseBuffer();
if (result)
MessageBox(tabCurrentCString); //success
It looks like you are using the recommended Unicode settings. Avoid converting UNICODE to ANSI (std::string). This conversion will work for Latin languages, most of the time, but it's not needed. You can use std::wstring if you need to use that in STL, or convert to UTF-8 if you want to send data to internet etc.
std::string str = CW2A(tabCurrentCString, CP_UTF8);

Wrong encoding when getting a string from MySQL database with C++

I'm writing an MFC app with C++ in Visual Studio 2012. App connects to a MySQL database and shows every row to a List Box.
Words are in Russian, database encoding is cp1251. I've set the same character set using this code:
if (!mysql_set_character_set(mysql, "cp1251")) {
statusBox.SetWindowText((CString)"CP1251 is set for MYSQL.");
}
But it doesn't help at all.
I display data using this code:
while ((row = mysql_fetch_row(result)) != NULL) {
CString string = (CString)row[1];
listBox.AddString(string);
}
This code also doesn't help:
mysql_query(mysql, "set names cp1251");
Please help. What should I do to display cyrillic correctly?
When crossing system boundaries that use different character encodings you have to convert between them. In this case, the MySQL database uses CP 1251 while Windows (and CString) use UTF-16. The conversion might look like this:
#if !defined(_UNICODE)
#error Unicode configuration required
#endif
CString CPtoUnicode( const char* CPString, UINT CodePage ) {
CString retValue;
// Retrieve required string length
int len = MultiByteToWideChar( CodePage, 0,
CPString, -1,
NULL, 0 );
if ( len == 0 ) {
// Error -> return empty string
return retValue;
}
// Allocate CString's internal buffer
LPWSTR buffer = retValue.GetBuffer( len );
// Do the conversion
MultiByteToWideChar( CodePage, 0,
CPString, -1,
buffer, len );
// Return control of the buffer back to the CString object
retValue.ReleaseBuffer();
return retValue;
}
This should be used as follows:
while ( ( row = mysql_fetch_row( result ) ) != NULL ) {
CString string = CPtoUnicode( row[1], 1251 );
listBox.AddString( string );
}
Alternatively, you could use CStrings built-in conversion support, which requires to set the thread's locale to the source encoding (CP 1251) and use the conversion constructor.

Display tooltips on controls on a PropertyPage. Crashes when returning TRUE to ON_NOTIFY_EX( TTN_NEEDTEXT...)

I have a class
class CCfgUserPage : public CPropertyPage
Which also owns various controls, from check boxes to text areas. I would like to add tooltips to each control, and seem to be having issues.
In CCfgUserPage I added this to the message map
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipText )
Which when this object catches that message it calls the function OnToolTipText which looks like this
BOOL CCfgUserPage::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
UINT nID = pNMHDR->idFrom;
CString ttStr;
int partOrient = GetDlgItem(IDC_PARTORIENT_CHECK)->GetDlgCtrlID();
if (pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if( nID == partOrient ) // Only Display TT for The buttons with these ID's
{
if( nID == partOrient )
ttStr = "Part Orient";
pTTT->lpszText = (LPTSTR)(LPCTSTR)ttStr;
pTTT->hinst = AfxGetResourceHandle();
return TRUE;
}
}
return FALSE;
}
I also enabled tool tips in
CCfgUserPage::OnInitDialog
Whenever OnToolTipText returns TRUE the application crashes and informs me of
Access violation reading location
I am trying to go through the stack frame but it is to far into MFC for me to understand what is going wrong. What might I be missing that would cause this to happen?
Have a look at the hint you have on MSDN:
When you handle the TTN_NEEDTEXT notification message, specify the
string to be displayed in one of the following ways:
Copy the text to the buffer specified by the szText member.
Copy the address of the buffer that contains the text to the lpszText member.
Copy the identifier of a string resource to the lpszText member, and copy the handle of the instance that contains the resource to the
hinst member.
So instead of doing:
CString ttStr;
// ...
if( nID == partOrient )
ttStr = "Part Orient";
// Below is the unsafe part: you initialize lpszText with something
// expected to be valid after you return from the handler
// effectively, this is internal buffer of local ttStr valriable
// which is to be freed and lpszText would keep point to undefined
// memory
pTTT->lpszText = (LPTSTR)(LPCTSTR)ttStr;
pTTT->hinst = AfxGetResourceHandle();
You would rather:
if(nID == partOrient)
{
// NOTE: Here instead you don't create any dynamic instances (strings)
// and the value resides directly in the notification structure
_tcsncpy_s(pTTT->szText, _T("Part Orient"), _TRUNCATE);
pTTT->lpszText = pTTT->szText; // Just a safety, it's already pointing there
}
The issue is that ttStr goes out of scope at the end of the function and you are returning a pointer to it. The pointer is now invalid and the app crashes when trying to reference it.
Use the supplied buffer if the tooltip will always be small (less than 80 characters) or use a member variable to store the tooltip text.

CComboBox::GetLBText returns garbage

I am filling a combobox:
while((pHPSet = pHPTable->GetNext()) != NULL)
{
CString str = pHPSet->GetName();
// I am normally using str but to proove that this is
// not the problem I am using "a"
m_comboBaseHP.AddString(_T("a"));
}
Now I am trying to read the combobox:
if(m_comboBaseHP.GetCount() > 0)
{
CString csHPName = _T("");
m_comboBaseHP.GetLBText(0, csHPName);
// This is the ms way but ReleaseBuffer causes a crash
//CString str = _T("");
//int n = m_comboBaseHP.GetLBTextLen( 0 );
//m_comboBaseHP.GetLBText( 0, str.GetBuffer(n) );
//str.ReleaseBuffer();
// Do whatever with csHPName
}
The problem is that csHPName shows in the Debugger some Chinese signs. I am assuming this is memory garbage. This happens in the same Method. This happens pre draw. Post draw the same issue. This happens in Debug and Release. I don't understand how this can happen since I am not actually working with pointers.
Apparently it is necessary to set the property Has Strings of the combobox to True.