Populate a Virtual ListView with std::vector<std::string> - c++

I have a vector of thousands of strings:
std::vector<std::wstring> a;
filled with some algorithms.
Following the method described here, here is how I create a ListView as a "virtual list":
hList = CreateWindowEx(0, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_OWNERDATA, 0, 0, 800, 400, hWnd, (HMENU)ID_LISTVIEW, hInst, NULL);
LV_COLUMN lvcol;
...
ListView_InsertColumn(hList, 0, &lvcol);
ListView_SetItemCountEx(hList, 100000, LVSICF_NOSCROLL);
...
// in the message loop
case WM_NOTIFY:
pdi = (NMLVDISPINFO*) lParam;
pi = pdi->item;
switch (pdi->hdr.code)
{
case LVN_GETDISPINFO:
{
pi.mask = LVIF_TEXT;
pi.pszText = a[pi.iItem]; // the nth item should be the nth string in the vector
}
}
I tried a lot of variations on:
pi.pszText = a[pi.iItem];
but they all failed with such kind of errors:
Error C2440: '=' : cannot convert from 'std::basic_string,std::allocator>' to 'LPWSTR'
What could help to do this?
Note: in fact I would like to display on row n of the ListView : the nth string of vector a concatenated with the number n, like this Blabla217 on the row 217.
Note2: even after Igor's suggestion (i.e. a cast pi.pszText = LPWSTR(a[pi.iItem].c_str());), the ListView is still empty, instead of displaying elements.

I'm not exact sure about the problem you're facing, but one thing for sure, you're passing multi-byte string (std::string, using char) while it is asking for wide-char string (std::wstring, using WCHAR).
Here is a handy code that converts std::string to std::wstring.
inline std::wstring WideFromMulti(
std::string const & multi,
UINT codepage)
{
int cchWide = MultiByteToWideChar(codepage, 0, multi.c_str(), -1, nullptr, 0);
LPWSTR szWide = new wchar_t[cchWide];
MultiByteToWideChar(codepage, 0, multi.c_str(), -1, szWide, cchWide);
std::wstring wide(szWide);
delete[] szWide;
return wide;
}
inline std::wstring WideFromUtf8(
std::string const & utf8)
{
return WideFromMulti(utf8, CP_UTF8);
}
Then you can get LPCWSTR by c_str().
std::string test_str;
std::wstring test_wstr = WideFromUtf8(test_str);
LPCWSTR wszTest = test_wstr.c_str();
What about LPWSTR? Well if you're sure that the string won't get modified, you can cast it by const_cast<LPWSTR>(wszTest). If you're strongly against const_cast, you may create a temporary copy of LPWSTR like this:
std::wstring test(L"Hello world");
LPCWSTR szTestConst = test.c_str();
int cchMax = ::lstrlenW(szTestConst) + 1;
std::vector<WCHAR> v(cchMax);
::lstrcpynW(&v[0], szTestConst, cchMax);
LPWSTR szTest = &v[0];

I don't really know why, but this solved it:
case WM_NOTIFY:
pdi = (NMLVDISPINFO*) lParam;
//pi = pdi->item;
switch (pdi->hdr.code)
{
case LVN_GETDISPINFO:
{
//pi.mask = LVIF_TEXT;
pdi->item.mask = LVIF_TEXT;;
//pi.pszText = a[pi.iItem];
pdi->item.pszText = a[pi.iItem];
}
}

Related

WinAPI GetWindowText as string

I was wondering if it were possible to take text input from a text box (CreateWindowEx "EDIT") and store it as a string or even better a vector
I need to have a textbox that the user can enter or paste text and when I click a button it will alphabetize the words and count unique words etc...
so far I have it reading it in as characters (i dont know how to make it a string) and alphabetizes the characters
so if I type in: how now brown cow
it will output: bchnnoooorwwww
instead of: brown cow how now
my code under the WM_COMMAND case is
int length;
length = GetWindowTextLength(textbox) + 1;
vector<wchar_t> list(length);
GetWindowText(textbox, &list[0], length);
wstring stxt = &list[0];
wstring str(stxt);
sort(str.begin(), str.end());
SetWindowText(sortedOutput, &str[0]);
This answer may be of use to you in devising a solution. I don't really know of one that is not hacky, but it can be done casting of the constness of c_string() from std::string.
https://stackoverflow.com/a/1986974/128581
A std::string's allocation is not guaranteed to be contiguous under the C++98/03 standard, but C++11 forces it to be. In practice, neither I nor Herb Sutter know of an implementation that does not use contiguous storage.
Notice that the &s[0] thing is always guaranteed to work by the C++11 standard, even in the 0-length string case. It would not be guaranteed if you did str.begin() or &*str.begin(), but for &s[0] the standard defines operator[] as:
Returns: *(begin() + pos) if pos < size(), otherwise a reference to an object of type T with value charT(); the referenced value shall not be modified
Continuing on, data() is defined as:
Returns: A pointer p such that p + i == &operator for each i in [0,size()].
(notice the square brackets at both ends of the range)
Thus it follows you can do something like this:
int len = GetWindowTextLength(hwnd) + 1;
std::string s;
s.reserve(len);
GetWindowText(hwnd, const_cast<char*>(s.c_str()), len - 1);
Which is pretty ugly. Welcome any more "correct" answers, though.
Regarding when unicode is enabled on your build, you have to use a wstring or equivalent. Testing that out just a moment ago, this works:
std::wstring title;
title.reserve(GetWindowTextLength(m_window_handle) + 1);
GetWindowText(m_window_handle, const_cast<WCHAR *>(title.c_str()), title.capacity());
In general, regarding the windows api its useful to google their all caps typedefs and figure out what they really are.
Regarding splitting strings, std::string isn't particular good at this kind of manipulation. This is where std::stringstream (or wstringstream for unicode) comes in handy. I am fairly certain stringstream is not guaranteed to be contiguous in memory, so you can't really go around writing directly into its buffer.
// Initialize a stringstream so we can extract input using >> operator
std::wstringstream ss;
ss.str(title);
// Make a vector, so we can store our words as we're extracting them
// and so we can use sort later, which works on many stl containers
std::vector<std::wstring> words;
std::wstring word;
// This will evaluate to false and thus end the loop when its done
// reading the string word by word
while(ss >> word)
{
words.push_back(word);
}
Then proceed with your sorting, but on the new vector words.
Your problem is not a winapi problem. While not the only way, you found a solution to transfer a string back and forth to your edit box.
How to turn that string into a list/vector of strings, with words being the elements of that list/vector is in fact an STL problem.
Basically, you are looking for the C++ equivalent of the C# function String.Split().
And there is already a good question and answer for that, here:
Most elegant way to split a string?
So, all you have to do is a sort of round trip:
Get string from TextBox
Convert string to a std::vector<string>, using your split function (see the other question for how to do that).
Sort the vector in the usual way.
Convert the vector back to a string, which is the inverse of your split function (Hint: std::ostringstream).
Set the resulting string as the text of your TextBox.
Depending on your preferences regarding multi character strings and globalization, you might decide to stick to the ASCII versions at first. On windows, you can compile to MBCS or ASCII. The respective string types are then respectively (TCHAR and LPCTSTR or WCHAR and LPCWSTR or CHAR and LPCSTR). All win32 functions come in two flavors, distinguished by a trailing A or W at the end of the function name, respectively.
AFAIK, while there is std::string and std::wstring, there is no standard implementation for std::basic_string<TCHAR>, which would work along with your compile options.
As for the window handling, here some code example (snippets):
In InitInstance(), I created the dialog (IDD_FORMVIEW) with my input edit box, my button and my static output area as a child window of the main application window:
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
HWND hWndChild = CreateDialogW(hInstance, MAKEINTRESOURCE(IDD_FORMVIEW), hWnd, dlgProc);
if (NULL == hWndChild)
{
DWORD lastError = GetLastError();
wchar_t msg[100];
swprintf_s(msg, _countof(msg), L"error code: 0x%0X\n", lastError);
OutputDebugString(msg);
}
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
ShowWindow(hWndChild, SW_SHOW);
UpdateWindow(hWnd);
return TRUE;
}
CreateDialogW() takes as the last parameter a pointer to the dialog handler function, which is called dlgProc() in this example.
This is how that dlgProc() function looks like:
// Message handler for our menu which is a child window of the main window, here.
static INT_PTR CALLBACK dlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
BOOL processed = false;
switch (uMsg)
{
case WM_INITDIALOG:
break;
case WM_NOTIFY:
break;
case WM_COMMAND:
{
auto sourceId = LOWORD(wParam);
auto code = HIWORD(wParam);
if (IDC_BUTTON1 == sourceId)
{
if (BN_CLICKED == code)
{
wchar_t text[1024];
GetDlgItemText(hwndDlg, IDC_EDIT1, text, _countof(text));
// TODO: do your string processing here (string -> string)
SetDlgItemText(hwndDlg, IDC_STATIC, text);
processed = TRUE;
}
}
}
break;
default:
break;
}
return processed;
}
I have just mixed a few lines of code to convert wchar_t to wstring to std::string. Here you go!
string GetWindowStringText(HWND hwnd)
{
int len = GetWindowTextLength(hwnd) + 1;
vector<wchar_t> buf(len);
GetWindowText(hwnd, &buf[0], len);
wstring wide = &buf[0];
string s(wide.begin(), wide.end());
return s;
}
This has a vector so you'll need to include it.

Getting weird characters from conversion of char to TCHAR

I'm getting really weird characters from the conversion. Any idea why? I get the "data" from an external device and i need to display it to win32 GUI. It had no problem when i
printf("%s\n",data);
in the console mode but having trouble when i migrate it to win32 which requires me to convert to TCHAR to display.
CHAR data[256];
TCHAR data1[256];
MultiByteToWideChar(CP_ACP,MB_COMPOSITE,data,-1,data1,0);
CreateWindow(TEXT("STATIC"), data1, WS_VISIBLE | WS_CHILD |
10, 50,300,300,hWnd, (HMENU) none, NULL, NULL);
By the way, using
hDLL=LoadLibrary("MyKad.dll");
in win32 couldn't work so I had to used
hDLL=LoadLibrary(TEXT("MyKad.dll"));
May I know is this right? Thanks
The reason that your code fails is that you pass 0 in the final parameter of MultiByteToWideChar. You can fix your code by passing the length if data1:
MultiByteToWideChar(CP_ACP, MB_COMPOSITE, data, -1, data1, 256);
Note also that you should be checking for errors when calling API functions. Had you done so you would have discovered that MultiByteToWideChar was failing.
I use the following function to convert to UTF-16:
std::wstring MultiByteStringToWideString(const std::string& MultiByte,
const UINT CodePage)
{
std::wstring result;
int cchWideChar = MultiByteToWideChar(CodePage, 0, MultiByte.c_str(), -1,
NULL, 0);
if (cchWideChar > 0)
{
wchar_t* bufferW = new wchar_t[cchWideChar];
if (MultiByteToWideChar(CodePage, 0, MultiByte.c_str(), -1, bufferW,
cchWideChar) != 0)
{
result = std::wstring(bufferW);
}
delete[] bufferW;
}
return result;
}
So you could use this as follows:
std::wstring windowName = MultiByteStringToWideString(data, CP_ACP);
CreateWindow(L"STATIC", windowName.c_str(), ...);

Issue with Int to LPWSTR function

I am making a win32 program that is a level editing tool to go with the library I am creating for a 2D tile system.
I want to create dialog box displaying the maps properties when the user selects it from the menu. This means a conversion from int to a wchar_t array. I have created a function that I hoped would do this. However currently it just returns a blank string that the return variable is initialized as. This conversion is necessary to work with the SetDlgItemText() function called by the map properties dialog box.
Here is the function I have currently:
LPWSTR IntToLPWSTR(int value)
{
std::ostringstream convert;
std::string out;
convert << value;
out = convert.str();
const char* in;
in = out.c_str();
LPWSTR ret = L"";
MultiByteToWideChar(CP_ACP, MB_COMPOSITE, in, strlen(in), ret, wcslen(ret));
return ret;
}
It is being called from here:
case WM_INITDIALOG:
if (mapToEdit)
{
SetDlgItemText(hDlg, IDC_TILE_WIDTH_LBL, IntToLPWSTR(mapToEdit->TileWidth()));
SetDlgItemText(hDlg, IDC_TILE_HEIGHT_LBL, L"");
SetDlgItemText(hDlg, IDC_MAP_WIDTH_LBL, L"");
SetDlgItemText(hDlg, IDC_MAP_HEIGHT_LBL, L"");
}
else
{
EndDialog(hDlg, LOWORD(wParam));
MessageBox(hWnd, L"You must create a map first", L"Error", 1);
}
Map to edit is simply a pointer to my own map class that contains the properies I want to display. The bottom three calls to SetDlgItemText() pass L"" as their string, the intention is that they will also use the function when it works.
std::to_wstring is simpler, but to point out the problem in your code, you never created a buffer. LPWSTR ret = L""; makes ret a pointer to an array held in static memory. This array cannot be modified.
Here is one way to fix the code by using std::wstring as the buffer:
std::wstring IntToWstring(int value)
{
std::ostringstream convert;
std::string out;
convert << value;
out = convert.str();
std::wstring ret;
// Find proper length
int length = MultiByteToWideChar(CP_ACP, 0, out.c_str(), out.length(), nullptr, 0);
ret.resize(length);
// Probably should also check for errors (got rid of MB_COMPOSITE flag)
MultiByteToWideChar(CP_ACP, 0, out.c_str(), out.length(), &ret[0], length);
return ret;
}
If you don't want to use std::wstring you could dynamically allocate a buffer LPWSTR ret = new LPWSTR[length];.
EDIT
Also, keep in mind that you could simplify the code to the following:
std::wstring IntToWstring(int value)
{
std::wostringstream convert;
convert << value;
convert.str();
}
You don't need to go to a lot of effort to convert an int into a const wchar_t *. Since C++11, you can take a two-step approach to a std::wstring and a const wchar_t * from there:
SetDlgItemText(hDlg, IDC_TILE_WIDTH_LBL, std::to_wstring(mapToEdit->TileWidth()).c_str());
Sure you could put that into a function to make it one step, but keep in mind that you cannot let the std::wstring be destroyed by the time you use the pointer.

win32 WM_SETTEXT not working

i made a small textbox like this
EBX = CreateWindow(TEXT("EDIT"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_NUMBER | WS_BORDER,
client.right - offset[1] - 200, client.top + offset[2] - 27,
45, 25, hwnd, (HMENU)ID_EDIT_SPEED, NULL, NULL);
everything is fine there but when i try to change the text inside like this i got some problems
SendMessage(EBX, WM_SETTEXT, 0, (LPARAM)"12"); // working
int a = 40;
SendMessage(EBX, WM_SETTEXT, 0, (LPARAM)a); // not working
any idea what is wrong ?
40 is not a string, "40" is.
If you want to convert a number to a string you must use a function like sprintf, etc.
E.g.
int a = 40;
char str[20];
StringCchPrintf(str, _countof(str), "%ld", a);
SendMessage(EBX, WM_SETTEXT, 0, (LPARAM)str);
You cannot, blindly typecast int to char*, use sprintf, stringstream or std::to_string to create string that holds literal representation of int value.
Or if you want to otput char with value 40 you need to pass pointer to null terminate array of chars. Like
char str[2];
str[0]=40;
str[1]=0;
convert 40 to c-string and use it in sendmessage function
char buffer [33];
int i =40;
itoa (i,buffer,10);
SendMessage(EBX, WM_SETTEXT, 0, (LPARAM)buffer);

CRichEditCtrl::GetSelText() is not working right

MFC File: winctrl4.cpp
(C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc)
CString CRichEditCtrl::GetSelText() const
{
ASSERT(::IsWindow(m_hWnd));
CHARRANGE cr;
cr.cpMin = cr.cpMax = 0;
::SendMessage(m_hWnd, EM_EXGETSEL, 0, (LPARAM)&cr);
CStringA strText;
LPSTR lpsz=strText.GetBufferSetLength((cr.cpMax - cr.cpMin + 1)*2);
lpsz[0] = NULL;
::SendMessage(m_hWnd, EM_GETSELTEXT, 0, (LPARAM)lpsz);
strText.ReleaseBuffer();
return CString(strText);
}
I am having a weird problem, when I call this it only returns the first character of the selected string. cr is correctly being set but after ::SendMessage(m_hWnd, EM_GETSELTEXT,... the whole string is not present.
I saw similar behavior in my custom code due to WCHAR issues (two-byte character containing a zero in one byte) when CHAR was expected. But this is part of MFC/Win32! Is it possible my .rc file sets something wrong? Is there a Create style relating to this? Or since we create a CFont for the control in question, could that screw it up?
This is not the correct MFC source code, have you edited it? Using CStringA and LPSTR is quite inappropriate, the real code uses CString and LPTSTR so that Unicode is correctly handled. Yes, as posted the code would only return one character.
Seeing the version helped. The bug is described in this feedback article. If you can't reasonably upgrade to VS2008 SP1, you could derive your own class from CRichEditCtrl and replace the function. For example:
CString CRichEditCtrlFix::GetSelText() const
{
ASSERT(::IsWindow(m_hWnd));
CHARRANGE cr;
cr.cpMin = cr.cpMax = 0;
::SendMessage(m_hWnd, EM_EXGETSEL, 0, (LPARAM)&cr);
CString strText;
LPTSTR lpsz=strText.GetBufferSetLength((cr.cpMax - cr.cpMin + 1) * 2);
lpsz[0] = NULL;
::SendMessage(m_hWnd, EM_GETSELTEXT, 0, (LPARAM)lpsz);
strText.ReleaseBuffer();
return CString(strText);
}
To get a wide char string you have to use the EM_GETTEXTEX message. CRichEditCtrl source does not contain a method which utilizes such message.
Here is a correct implementation of GetSelText() which actually does return Unicode characters:
CString CRichEditCtrlFix::GetSelText() const
{
ASSERT(::IsWindow(m_hWnd));
CHARRANGE cr;
cr.cpMin = cr.cpMax = 0;
::SendMessage(m_hWnd, EM_EXGETSEL, 0, (LPARAM)&cr);
CString strText;
int sz = (cr.cpMax - cr.cpMin + 1) * sizeof(tchar);
LPTSTR lpsz = strText.GetBufferSetLength(sz);
lpsz[0] = NULL;
GETTEXTEX gte;
memset( &gte, 0, sizeof(GETTEXTEX) );
gte.cb = sz;
gte.flags = GT_SELECTION;
if( sizeof(tchar) == 2 ) gte.codepage = 1200;
::SendMessage(m_hWnd, EM_GETTEXTEX, (WPARAM)&gte, (LPARAM)lpsz);
strText.ReleaseBuffer();
return CString(strText);
}
1200 here means UTF-16LE