I have a ListView control with 4 columns that is initialized in the WM_CREATE proc.
hListView1 = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL, WS_CHILD|WS_VSCROLL|WS_HSCROLL|WS_VISIBLE|LVS_REPORT|LVS_SHOWSELALWAYS, 230, 20, 300, 250, hwnd, (HMENU)ID_EDIT1, GetModuleHandle(NULL), NULL);
ListView_SetExtendedListViewStyle(hListView1, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
lvCol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvCol.fmt = LVCFMT_LEFT;
lvCol.iSubItem=0;
lvCol.cx=30;
lvCol.pszText="";
ListView_InsertColumn(hListView1, 0, &lvCol);
lvCol.iSubItem=1;
lvCol.cx=150;
lvCol.pszText="Name";
ListView_InsertColumn(hListView1, 1, &lvCol);
lvCol.iSubItem=2;
lvCol.cx=50;
lvCol.pszText="Size";
ListView_InsertColumn(hListView1, 2, &lvCol);
lvCol.iSubItem=3;
lvCol.cx=80;
lvCol.pszText="Modified";
ListView_InsertColumn(hListView1, 3, &lvCol);
Then i have a function that will insert the items (it works fine until i call deleteallitems)
...
LVITEM lvItem;
j = 0;
while(FindNextFile(hFind,&FindFileData)){
lvItem.iItem = j;
lvItem.iImage = 1;
if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
lvItem.iImage = 0;
}
ListView_InsertItem(hListView1, &lvItem);
ListView_SetItemText(hListView1, j, 1, FindFileData.cFileName);
ListView_SetItemText(hListView1, j, 2, msg1);
ListView_SetItemText(hListView1, j, 3, msg2);
j++;
}
But then whenever i call
ListView_DeleteAllItems(hListView1);
if after i call my function that insert items, my listview is cleared (the columns are still there) but no new items are insered..
I heard about indexes that are not cleared but i couldnt figured it out.
Thanks in advance ;-)
Solution :
Added
lvItem.mask = LVIF_IMAGE | LVIF_STATE;
lvItem.state = 0;
lvItem.stateMask = 0;
lvItem.iSubItem = 0;
You are not setting lvItem.mask, so ListView_InsertItem doesn't know which fields are valid and which aren't.
Try something like this:
...
LVITEM lvItem;
lvItem.mask = LVIF_IMAGE | LVIF_DI_SETITEM;
j = 0;
...
Related
I'm working with Comctl32.dll's list-view control and attempting to divide the width of it amongst 10 columns, auto-sizing to match the strings in their respective headers. I am using the ListView_SetColumnWidth macro with the LVSCW_AUTOSIZE_USEHEADER value, and it's working with every column except the first one:
Here's the code I think is relevant, but please let me know if it's not enough:
HWND hListView;
hListView = CreateWindowEx(
WS_EX_CLIENTEDGE,
WC_LISTVIEW,
L"",
WS_CHILD | WS_VISIBLE |
LVS_REPORT,
200, 10, 800, 150,
hwnd,
(HMENU)IDC_MAIN_LV,
GetModuleHandle(NULL),
NULL);
if (hListView == NULL) {
MessageBox(hwnd, L"Could not create list box.", L"Error!", MB_OK | MB_ICONERROR);
}
LVCOLUMN lvc;
WCHAR buffer[256];
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
for (int col = 0; col < C_COLUMNS; col++) {
lvc.iSubItem = col;
lvc.pszText = buffer;
lvc.cx = 100;
lvc.fmt = LVCFMT_CENTER;
LoadString(
GetModuleHandle(NULL),
IDS_HP + col,
buffer,
sizeof(buffer));
if (ListView_InsertColumn(hListView, col, &lvc) == -1) {
MessageBox(hwnd, L"Could not create list box.", L"Error!", MB_OK | MB_ICONERROR);
}
ListView_SetColumnWidth(hListView, col, LVSCW_AUTOSIZE_USEHEADER);
}
SetFocus(hListBox);
This is my first project after learning the basics of C++ so I'm sure I'm doing everything shown here the worst way possible. (: Specifically, I'd like to know what's causing this particular error please. Thanks for reading!
From the ListView_SetColumnWidth documentation:
LVSCW_AUTOSIZE_USEHEADER
Automatically sizes the column to fit the header text. If you use this value with the last column, its width is set to fill the remaining width of the list-view control.
When you use this for the first time, there's only one column, so it is the last column and therefore gets resized to the full width of the listview.
The solution is to add all your columns first and then autosize them.
for (int col = 0; col < C_COLUMNS; col++) {
lvc.iSubItem = col;
lvc.pszText = buffer;
lvc.cx = 100;
lvc.fmt = LVCFMT_CENTER;
LoadString(
GetModuleHandle(NULL),
IDS_HP + col,
buffer,
sizeof(buffer));
ListView_InsertColumn(hListView, col, &lvc);
}
for (int col = 0; col < C_COLUMNS; col++) {
ListView_SetColumnWidth(hListView, col, LVSCW_AUTOSIZE_USEHEADER);
}
My goal is to create a row of dynamic controls every time i click the Add button (on run-time), like this:
| Combo box | |Add Button|
|Static Ctrl| |Edit Ctrl| |Edit Ctrl| |Edit Ctrl| |Delete Button|
|Static Ctrl| |Edit Ctrl| |Edit Ctrl| |Edit Ctrl| |Delete Button|(*)
|Static Ctrl| |Edit Ctrl| |Edit Ctrl| |Edit Ctrl| |Delete Button|
and for example if i clicked on the Delete Button(*) , it will delete the whole row (including that Delete button). Then after that when click Add again, the newly created row will appeared at the same position, or even better if i could make all the rows below to move up, an the newly added row will appeared at the bottom.
Here are some code i wrote:
int CSettingDlg::Getid() // increase the id by 1 each time it was called
{
id = id + 1; // int id = 4000 in the '.h' file
return id;
}
int CSettingDlg::AddControlSet() // Add a row of control
{
int index = 0;
indexStr.Format(_T("%d"), index + 1);
GetDlgItem(IDC_TEST1)->GetWindowRect(&rcCtrl);
ScreenToClient(&rcCtrl);
for (;;)
{
rcCtrl.top = rcCtrl.top + index * 35;
rcCtrl.bottom = rcCtrl.bottom + index * 35;
StaticText = new CStatic;
EditBox = new CEdit;
EditBox2 = new CEdit;
EditBox3 = new CEdit;
Delete = new CButton;
StaticText->Create((indexStr), WS_CHILD | WS_VISIBLE | ES_READONLY | SS_NOTIFY, CRect(rcCtrl.left -= 163, rcCtrl.top += 5, rcCtrl.right -= 270, rcCtrl.bottom), this, Getid());
EditBox->Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(rcCtrl.left += 28, rcCtrl.top -= 5, rcCtrl.right += 134, rcCtrl.bottom), this, Getid());
EditBox2->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY, CRect(rcCtrl.left += 135, rcCtrl.top, rcCtrl.right += 136, rcCtrl.bottom), this, Getid());
EditBox3->Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(rcCtrl.left += 135, rcCtrl.top, rcCtrl.right += 172, rcCtrl.bottom), this, Getid());
Delete->Create(_T("Del"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(rcCtrl.left += 191, rcCtrl.top, rcCtrl.right += 101, rcCtrl.bottom), this, Getid());
index++;
return TRUE;
nCount++;
}
}
And here is my basic idea to delete the row:
void CSettingDlg::OnBnClickedDeleteSettingDlg(UINT nID)
{
nID == Getid();
switch (nID)
{
case 3005: //3005 is the 1st Delete Button's ID
for (; nID > 3000; nID--)
GetDlgItem(nID)->DestroyWindow(); //destroy all controls that have ID from 3001 to 3005
nCount--; //This variable is not relevant
break;
case 3010: //The 2nd Delete Button's ID
...
}
I know my codes are pretty bad, so if anybody have a solution, hint to my question or at least know to make my code a little better i would be very appreciated.
You can use a vector to dynamically add/remove buttons. Note that each new CWnd pointer needs a corresponding delete to avoid resource leaks, so make sure to clean up when buttons are deleted. For simplicity you can put the controls for each row in a structure, as shown in the example below. You also have to reposition the controls when a row is added or deleted.
struct control_set
{
CStatic st;
CEdit e1, e2, e3;
CButton bn;
};
std::vector<control_set*> vec;
CSettingDlg::~CSettingDlg()
{
//for(auto &e : vec) delete e; <- remove this
}
void CSettingDlg::PostNcDestroy() // <- add this
{
CDialog::PostNcDestroy();
for(auto &e : vec)
delete e;
vec.clear();
}
void CSettingDlg::AddControlSet()
{
vec.push_back(new control_set);
vec.back()->st.Create(_T("text"), WS_CHILD | WS_VISIBLE, CRect(0, 0, 0, 0), this, 0);
vec.back()->e1.Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(0, 0, 0, 0), this, 0);
vec.back()->e2.Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(0, 0, 0, 0), this, 0);
vec.back()->e3.Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(0, 0, 0, 0), this, 0);
vec.back()->bn.Create(_T("del"), WS_CHILD | WS_VISIBLE, CRect(0, 0, 0, 0), this, 0);
resize_controls();
}
void CSettingDlg::OnBnClickedDeleteSettingDlg(UINT id)
{
UINT row = id / 100;
//add more checks to make sure this is the ID from delete buttons
if(row < 0 || row >= vec.size()) return;
delete vec[row];
vec.erase(vec.begin() + row);
resize_controls();
}
void CSettingDlg::resize_controls()
{
CRect rc(10, 20, 10 + 50, 20 + 14);
MapDialogRect(&rc);
for(size_t i = 0; i < vec.size(); i++)
{
std::vector<CWnd*> tmp{
&vec[i]->st, &vec[i]->e1, &vec[i]->e2, &vec[i]->e3, &vec[i]->bn};
CRect r = rc;
for(size_t j = 0; j < tmp.size(); j++)
{
tmp[j]->MoveWindow(r);
tmp[j]->SetDlgCtrlID(i * 100 + j);
tmp[j]->SetFont(GetFont());
r.OffsetRect(rc.Width() + 2, 0);
}
rc.OffsetRect(0, rc.Height() + 2);
}
}
And you want to add message handler for delete buttons:
ON_COMMAND_RANGE(4, 904, OnBnClickedDeleteSettingDlg)
Alternatively you can set the position for the control as follows. First, put 5 dummy static controls in the dialog resource, with the following IDs:
| IDC_REF_STATIC | IDC_REF_EDIT1 | IDC_REF_EDIT2 | IDC_REF_EDIT3 | IDC_REF_BUTTON |
These controls can be hidden.
Then use the coordinates of these dummy controls to position the new controls. You can just offset the rectangle to go to the next row.
void CSettingDlg::resize_controls()
{
CWnd *st = GetDlgItem(IDC_REF_STATIC);
CWnd *e1 = GetDlgItem(IDC_REF_EDIT1);
CWnd *e2 = GetDlgItem(IDC_REF_EDIT2);
CWnd *e3 = GetDlgItem(IDC_REF_EDIT3);
CWnd *bn = GetDlgItem(IDC_REF_BUTTON);
ASSERT(st && e1 && e2 && e3 && bn);
CRect r, rc;
st->GetWindowRect(&rc);
ScreenToClient(&rc);
for(size_t i = 0; i < vec.size(); i++)
{
//reposition the static control
st->GetWindowRect(&r);
ScreenToClient(&r);
r.MoveToY(rc.top);
vec[i]->st.MoveWindow(r);
//edit1
e1->GetWindowRect(&r);
ScreenToClient(&r);
r.MoveToY(rc.top);
vec[i]->e1.MoveWindow(r);
//edit2
e2->GetWindowRect(&r);
ScreenToClient(&r);
r.MoveToY(rc.top);
vec[i]->e2.MoveWindow(r);
//edit3
e3->GetWindowRect(&r);
ScreenToClient(&r);
r.MoveToY(rc.top);
vec[i]->e3.MoveWindow(r);
//button
bn->GetWindowRect(&r);
ScreenToClient(&r);
r.MoveToY(rc.top);
vec[i]->bn.MoveWindow(r);
//move rc down by one row
rc.OffsetRect(0, rc.Height() + 2);
//Set the font for each control
//Also set id for each control, based on the row
vec[i]->st.SetDlgCtrlID(i * 100 + 1);
vec[i]->st.SetFont(GetFont());
vec[i]->e1.SetDlgCtrlID(i * 100 + 2);
vec[i]->e1.SetFont(GetFont());
vec[i]->e2.SetDlgCtrlID(i * 100 + 3);
vec[i]->e2.SetFont(GetFont());
vec[i]->e3.SetDlgCtrlID(i * 100 + 4);
vec[i]->e3.SetFont(GetFont());
vec[i]->bn.SetDlgCtrlID(i * 100 + 5);
vec[i]->bn.SetFont(GetFont());
}
}
I'm creating a SQL client with WinAPI. Every time a user submits a query, the current columns and items of my ListView are deleted, then new columns and items are created based on the results of the query.
I've noticed through Visual Studio heap profiler, that every time the columns and items are created, 3 objects and ~60 bytes are allocated, and I can't figure out why. Examining the snapshot suggests this has to do with adding SubItems via the ListView_SetItem function (_ListView_OnSetItem is the last thing I can identify in Stacks View).
The results I'm using to test with create 4 columns and 36 items/subitems.
To the best of my knowledge, I've implemented the ListView stuff correctly. Still, here are my two functions that reset and populate the ListView. Any help is appreciated.
void ResetListView(HWND hWnd) {
HWND hWndHdr = (HWND)SendMessage(GetDlgItem(hWnd, IDQ_LISTVIEW), LVM_GETHEADER, 0, 0);
int numColumns = (int)SendMessage(hWndHdr, HDM_GETITEMCOUNT, 0, 0L);
ListView_DeleteAllItems(GetDlgItem(hWnd, IDQ_LISTVIEW));
for (int i = 0; i < numColumns; i++)
ListView_DeleteColumn(GetDlgItem(hWnd, IDQ_LISTVIEW), 0);
}
void CreateListView(HWND hWnd, QueryResults * queryResults) {
LVCOLUMN lvc;
ZeroMemory(&lvc, sizeof(LVCOLUMN));
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_CENTER;
int numHeaders = queryResults->numHeaders;
// Add columns
for (int i = 0; i < numHeaders; i++) {
lvc.iSubItem = i;
lvc.pszText = queryResults->headers[i];
lvc.cx = 100;
ListView_InsertColumn(GetDlgItem(hWnd, IDQ_LISTVIEW), i, (LPARAM)&lvc);
}
LVITEM lvi;
ZeroMemory(&lvi, sizeof(LVITEM));
lvi.mask = LVIF_TEXT;
// Add items and subitems
for (int i = 0; i < queryResults->rows.size(); i++) {
lvi.iItem = i;
lvi.pszText = queryResults->rows[i]->cells[0];
lvi.iSubItem = 0;
ListView_InsertItem(GetDlgItem(hWnd, IDQ_LISTVIEW), (LPARAM)&lvi);
for (int j = 1; j < numHeaders; j++) {
lvi.pszText = queryResults->rows[i]->cells[j];
lvi.iSubItem = j;
ListView_SetItem(GetDlgItem(hWnd, IDQ_LISTVIEW), (LPARAM)&lvi);
}
}
return;
}
I have a straight win32 c++ app and I'm filling the window with a ListView whose view type is set to LV_VIEW_TILE and I'm also setting the style to LVS_OWNERDATA.
I'm having trouble trying to work out how to get the subitems to show. This code creates the view.
DWORD exstyle =WS_EX_CLIENTEDGE|LVS_EX_DOUBLEBUFFER|LVS_EX_JUSTIFYCOLUMNS|LVS_EX_INFOTIP;
g_hwndList = CreateWindowEx(exstyle, WC_LISTVIEW, NULL, WS_CHILD | WS_VISIBLE | LVS_ICON | LVS_OWNERDATA, 0, 0, 0, 0, hWnd, (HMENU) 2702, hInst, NULL);
ListView_SetView(g_hwndList, LV_VIEW_TILE);
LVTILEVIEWINFO tileViewInfo = { };
tileViewInfo.cbSize = sizeof(LVTILEVIEWINFO);
tileViewInfo.dwFlags = LVTVIF_AUTOSIZE;
tileViewInfo.dwMask = LVTVIM_COLUMNS;
tileViewInfo.cLines = 1;
BOOL tst = ListView_SetTileViewInfo(g_hwndList, &tileViewInfo);
I only want one more subitem/column to appear. In my LVN_GETDISPINFO I currently have this:
static int colfmt[1];
colfmt[0] = LVCFMT_LEFT;
static int order[1];
order[0] = 1;
if ((nimfo->item.mask & LVIF_COLUMNS) == LVIF_COLUMNS) {
nimfo->item.cColumns = 1;
nimfo->item.piColFmt = PINT(colfmt);
nimfo->item.puColumns = PUINT(order);
}
if ((nimfo->item.mask & LVIF_TEXT) == LVIF_TEXT) {
nimfo->item.pszText = di->LABEL;
}
if ((nimfo->item.mask & LVIF_IMAGE) == LVIF_IMAGE) {
nimfo->item.iImage = di->IMAGE_INDEX;
}
I can't work out at what point and where I need to supply the subitem/column text, I'm never seeing the nimfo->item.subitem changing from 0 and for each call for LVIF_TEXT the structure values are always the same.
So at what point do I need to supply the extra textual data?
Many thanks.
I, rather stupidly, wasn't adding any columns and therefore wasn't being asked for the other items.
added this and everything works
LVCOLUMN col = {};
col.mask = LVCF_SUBITEM;
col.iSubItem = 0;
ListView_InsertColumn(g_hwndList, 0, &col);
I was thinking i could use the LVITEM structures LPARAM to attach a pointer to my class, but i can't seem to get it to work!
Heres the main parts of my code:
Creating the listview:
hlvQuiz = CreateChild(WC_LISTVIEW, "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LVS_ICON | LVS_AUTOARRANGE,
0, 0, 320, 240, m_hwnd, FontNormal);
Adding items:
if (vQuizes.size() > 0)
{
LVITEM lvi;
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = 0;
lvi.iSubItem = 0;
lvi.cchTextMax = QUIZSTRLEN;
for (unsigned int i = 0; i < vQuizes.size(); i++)
{
lvi.lParam = (LPARAM)&vQuizes[i]; // adding pointer to lparam
lvi.pszText = vQuizes[i].szName;
ListView_InsertItem(hlvQuiz, &lvi);
}
}
Then later when i go to get my class back from the LPARAM:
LVITEM lvi;
lvi.iItem = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
lvi.iSubItem = 0;
if (ListView_GetItem(fm->hlvQuiz, &lvi) == TRUE)
{
Quiz* q = (Quiz*)lvi.lParam;
if (q != NULL) // i get stopped here because my pointer is NULL
if (Exists(q->szPath) == IS_FILE)
ShellExecute(NULL, "edit", q->szPath, NULL, NULL, SW_SHOWNORMAL);
}
Is there anything that i'm doing wrong? the listview creates fine, and the items add, but the pointer to my class which i put in the LPARAM value seems to be ignored, or changed by the time i come to dereference it
I haven't worked at this low level before, but I suspect you need to set the mask member of the LVITEM structure to LVIF_PARAM (as well as the appropriate values for anything else you need) for the call to ListView_GetItem.
Your code works fine in Debug mode but not in Release mode because
you missed to specify the name of LPARAM in lvi.mask (and the name of any other field you want back).
Try this:
lvi.iItem = ListView_GetNextItem(hlvQuiz, -1, LVNI_SELECTED);
lvi.mask = LVIF_PARAM;
if (ListView_GetItem(fm->hlvQuiz, &lvi) == TRUE) ...
You'll receive a copy of the LPARAM's value you've setted. I think that this little strange behaviour is due to the Debug mode's help that initalize everything for you. The Release mode instead do not.