I'm quite new to WinAPI, so I sometimes make a lot of basic mistakes. You've been warned, let's move to my problem :P/
I want to make something like a grid. If the user click one of the grid's fields, there should appear a bitmap. I've made a bitmap of field that I want to use as a button. At the start user inputs a size of the grid, so I've made a dynamic library of buttons with that bitmap. Unfortunately, I have no idea how to deal with them when they are clicked. Here's my code:
//there I create my window. I also make a global variable bool* table and HWND next.
table = new bool[x*y];
for (int i = 0; i < x*y; ++i)
table[i] = 0;
//the table represents if the fields are already filled or not.
HWND* buttons = new HWND[x*y];
next = CreateWindowEx(4, _T("BUTTON"), _T("MOVE"), WS_CHILD | WS_VISIBLE, x * 12 - 25, (y + 4) * 25 - 90, 100, 50, hwnd, NULL, hThisInstance, NULL);
for (int i = 0; i < x; ++i)
{
for (int j = 0; j < y; ++j)
{
buttons[i + j * x] = CreateWindowEx(0, _T("BUTTON"), NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP, 0 + i * 25, 0 + j * 25, 25, 25, hwnd, NULL, hThisInstance, NULL);
SendMessage(przyciski[i + j * x], BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)field);
}
}
And now in WindowProcedure():
switch (message)
{
case WM_COMMAND:
if ((HWND)lParam == next) /* there will be some code in the future ;) */;
else
{
//So here I need to set correct the value in table to 1
//I have a handle to clicked button (HWND)lParam, but I don't know how to get it's position in the table
}
break;
I tried to do some struct with HWND and int x, y, but I still don't know how to manage this. BTW, the code might look very old, I need to create an app that Windows XP can run (it's a project for school, do not inquire :P) and in addition I use a very old tutorial.
Assign an ID for each button using the HMENU parameter. The ID cannot be zero, therefore add an arbitrary offset, for example 100:
buttons[i + j * x] = CreateWindowEx(0, _T("BUTTON"), NULL,
WS_CHILD | WS_VISIBLE | BS_BITMAP, 0 + i * 25, 0 + j * 25, 25, 25,
hwnd, HMENU(100 + i + j * x), hThisInstance, NULL);
You can also extract the row and column from button_index, knowing the total rows and total columns. Example:
case WM_COMMAND:
{
if (HIWORD(wParam) == BN_CLICKED)
{
int id = LOWORD(wParam);
int button_index = id - 100;
if (button_index >= 0 && button_index < x * y)
{
int row = button_index / x;
int column = button_index % x;
...
}
...
}
break;
}
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());
}
}
Say, I have a Win32 app with a menu added to the main window via CreateWindowEx method, and I need to know its usable width. The best way to illustrate the width I'm asking for is with this diagram:
So how do I calculate it?
When I try to do the following, it gives me the width of the client area:
MENUBARINFO mbi = {0};
mbi.cbSize = sizeof(mbi);
if(::GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi))
{
int nUsableWifth = mbi.rcBar.right - mbi.rcBar.left;
}
If you want to know the width that is used for menu items, you can either:
use GetMenuItemRect() to get the screen coordinates of the last menu item, and then convert them to client coordinates within the parent window. The converted right edge coordinate will give you the width:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
RECT r;
if (::GetMenuItemRect(hWnd, hMenu, count-1, &r))
{
::MapWindowPoints(NULL, hWnd, (LPPOINT)&r, 2);
int nUsedWidth = r.right;
...
}
the above assumes the menu starts at offset 0 within the window's client area. If you don't want to rely on that, you could instead get the screen coordinates of the 1st menu item and subtract it from the right-edge screen coordinate of the last menu item:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
RECT rFirst, rLast;
if (::GetMenuItemRect(hWnd, hMenu, 0, &rFirst) &&
::GetMenuItemRect(hWnd, hMenu, count-1, &rLast))
{
int nUsedWidth = rLast.right - rFirst.left;
...
}
Either way, if you then want to know the width that is not used for menu items, simply get the menu's total width and subtract the above calculated width:
MENUBARINFO mbi = {0};
mbi.cbSize = sizeof(mbi);
if (::GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi))
{
int nUsableWidth = (mbi.rcBar.right - mbi.rcBar.left) - nUsedWidth;
...
}
UPDATE: I didn't realize a window's menu can wrap its items vertically if the client area is too small to display them all on one line. In that case, you might have to do something more like this instead to calculate nUsedWidth:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
int nUsedWidth = 0;
RECT r;
for(int idx = 0; idx < count; ++idx)
{
if (::GetMenuItemRect(hWnd, hMenu, idx, &r))
{
::MapWindowPoints(NULL, hWnd, (LPPOINT)&r, 2);
if (r.right > nUsedWidth)
nUsedWidth = r.right;
}
}
...
Or:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
int nUsedWidth = 0;
RECT rFirst, r;
if (::GetMenuItemRect(hWnd, hMenu, 0, &rFirst))
{
nUsedWidth = rFirst.right - rFirst.left;
for (int idx = 1; idx < count; ++idx)
{
if (::GetMenuItemRect(hWnd, hMenu, idx, &r))
{
int nWidth = r.right - rFirst.left;
if (nWidth > nUsedWidth)
nUsedWidth = nWidth;
}
}
}
...
INTRODUCTION AND RELEVANT INFORMATION:
I am trying to implement listview control with editable items and subitems. Instead of regular listview look, items and subitems should have edit control, checkbox or combo box.
I am using raw WinAPI and C++. I am targeting Windows XP onwards.
MY EFFORTS TO SOLVE THE PROBLEM:
After researching here and on the Internet, I was able to only find examples in MFC. They all use LVN_BEGINLABELEDIT technique to implement this behavior.
Unfortunately I do not understand entirely this concept so I have decided to start from scratch ( I consider this also to be the best approach for improving ones programming skills ).
MY CONCEPT:
I have decided to catch NM_DBLCLK for listview and to get coordinates from there using ListView_GetItemRect or ListView_GetSubItemRect macro.
Then I would simply move the combobox/checkbox/edit control over corresponding item/subitem ( combobox/edit control/checkbox would be created as separate, hidden windows ).
After user finishes with input ( by pressing enter or changing focus ) I would simply hide the combobox/checkbox/edit control.
MY CURRENT RESULTS:
At the moment, I am stuck with the dimensions of combobox/edit control/checkbox not being the same as item/subitem dimensions, when moved above the item/subitem.
QUESTION:
Can my code example submitted below be improved to properly adjust combobox/edit control/checkbox window size to the size of the item/subitem? For now, I will only focus on this part of the problem, to keep this question as short as possible.
Here is the instruction for creating small application that illustrates the problem. Notice that I have tried to keep things as minimal as I could:
1.) Create default Win32 project in Visual Studio ( I use VS 2008 ).
2.) Add the following WM_CREATE handler to main window's procedure:
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0,WC_EDIT, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER | ES_AUTOHSCROLL,
250, 10, 100, 20, hWnd, (HMENU)1500, hInst, 0 );
HWND hComboBox = CreateWindowEx( 0,WC_COMBOBOX, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWNLIST,
100, 10, 100, 20, hWnd, (HMENU)1600, hInst, 0 );
HWND hwndLV = CreateWindowEx( 0, WC_LISTVIEW,
L"Editable Subitems",
WS_CHILD | WS_VISIBLE | WS_BORDER |
LVS_REPORT | LVS_SINGLESEL,
150, 100, 250, 150, hWnd, (HMENU)2000, hInst, 0 );
// set extended listview styles
ListView_SetExtendedListViewStyle( GetDlgItem( hWnd, 2000 ),
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER );
// add some columns
LVCOLUMN lvc = {0};
lvc.iSubItem = 0;
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
for (long nIndex = 0; nIndex < 5; nIndex++ )
{
wchar_t txt[50];
swprintf_s( txt, 50, L"Column %d", nIndex + 1 );
lvc.iSubItem = nIndex;
lvc.cx = 60;
lvc.pszText = txt;
ListView_InsertColumn( GetDlgItem( hWnd,2000 ), nIndex, &lvc );
}
// add some items
LVITEM lvi;
lvi.mask = LVIF_TEXT;
lvi.iItem = 0;
for( lvi.iItem = 0; lvi.iItem < 10; lvi.iItem++ )
for (long nIndex = 0; nIndex < 5; nIndex++ )
{
wchar_t txt[50];
swprintf_s( txt, 50, L"Item %d%d", lvi.iItem + 1, nIndex + 1 );
lvi.iSubItem = nIndex;
lvi.pszText = txt;
if( ! nIndex ) // item
SendDlgItemMessage( hWnd, 2000,
LVM_INSERTITEM, 0,
reinterpret_cast<LPARAM>(&lvi) );
else // sub-item
SendDlgItemMessage( hWnd, 2000,
LVM_SETITEM, 0,
reinterpret_cast<LPARAM>(&lvi) );
}
}
return 0L;
3.) Add the following handler for WM_NOTIFY in main window's procedure:
case WM_NOTIFY:
{
if( ((LPNMHDR)lParam)->code == NM_DBLCLK )
{
switch( ((LPNMHDR)lParam)->idFrom )
{
case 2000: // remember, this was our listview's ID
{
LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam;
// SHIFT/ALT/CTRL/their combination, must not be pressed
if( ( lpnmia->uKeyFlags || 0 ) == 0 )
{
// this is where we store item/subitem rectangle
RECT rc = { 0, 0, 0, 0 };
if( (lpnmia->iSubItem) <= 0 ) // this is item so we must call ListView_GetItemRect
{
// this rectangle holds proper left coordinate
// since ListView_GetItemRect with LVIR_LABEL flag
// messes up rectangle's left cordinate
RECT rcHelp = { 0, 0, 0, 0 };
// this call gets the length of entire row
// but holds proper left coordinate
ListView_GetItemRect( lpnmia->hdr.hwndFrom,
lpnmia->iItem, &rcHelp, LVIR_BOUNDS );
// this call gets proper rectangle except for the left side
ListView_GetItemRect( lpnmia->hdr.hwndFrom,
lpnmia->iItem, &rc, LVIR_LABEL );
// now we can correct the left coordinate
rc.left = rcHelp.left;
}
else // it is subitem, so we must call ListView_GetSubItemRect
{
ListView_GetSubItemRect( lpnmia->hdr.hwndFrom,
lpnmia->iItem, lpnmia->iSubItem,
LVIR_BOUNDS, &rc );
}
// convert listview client coordinates to parent coordinates
// so edit control can be properly moved
POINT p;
p.x = rc.left;
p.y = rc.top;
ClientToScreen( lpnmia->hdr.hwndFrom, &p );
ScreenToClient( hWnd, &p );
MoveWindow( GetDlgItem( hWnd, 1500 ),
p.x, p.y,
rc.right - rc.left,
rc.bottom - rc.top, TRUE );
// set focus to our edit control
HWND previousWnd = SetFocus( GetDlgItem( hWnd, 1500 ) );
}
}
break;
default:
break;
}
}
}
break;
And this is the result I get:
You can clearly see that top and bottom border of the edit control are not drawn properly. As for combobox, the width is properly adjusted, but height remains the same.
I have tried substituting MoveWindow call with SetWindowPos but the result was the same.
After further tampering, I have found out that NMITEMACTIVATE bugs when returning the rectangle of a subitem, if listview doesn't have LVS_EX_FULLROWSELECT style set. You can see this by simply commenting out the part in my WM_CREATE handler where I set this style. Maybe I am doing something wrong and this "bug" may be caused by my code, but I don't see the problem.
EDITED on September, 17th 2014:
After testing the values for iItem and iSubItem members of NMITEMACTIVATE structure when listview doesn't have LVS_EX_FULLROWSELECT I can verify that the bug is not in my code. It always returns iItem to be 0, no matter which subitem I click. This explains the faulty behavior I got when removing this style.
If any further info is required please leave a comment and I will act as soon as possible.
Thank you for your time and efforts to help.
The issue you're facing is multi-faceted.
Firstly, the default font of the edit control is larger (higher) than that of the list-view. You can fix this one quite trivially, by first getting the font from the list-view and then setting it to the edit control. Doing this will then make the bottom border of the control visible.
The next issue is that the caret of the edit control needs a pixel above and below it, to ensure that the control doesn't have its borders interfered with. In addition to this 1 pixel of 'space' you then need another pixel for the border.
Added to this second point, the dimensions calculated by rc.right - rc.left and rc.bottom - rc.top are 1 pixel too small. Think of a rect that starts at 1,1 and extends to 2,2 - this is a rect of 4 pixels - 2 wide and 2 high. Simply subtracting the top/left from the bottom/right would give you a width/height of only 1 pixel each. To fix this, you need to add 1 to each of these subtractions.
Finally, since the caret is exactly the height of the 'client-area' of each item/sub-item, you need to make the edit control 2 pixels taller than the item/sub-item, and start 1 2 pixels higher than it does currently.
Here's the output I get when making the suggested changes:
And here's the changes/additions I made.
1. Get/Set the font. (inserted after creating the list-view and before setting its extended style)
HFONT lvFont = (HFONT)SendDlgItemMessage(hWnd, 2000, WM_GETFONT, 0, 0);
SendDlgItemMessage(hWnd, 1500, WM_SETFONT, (WPARAM)lvFont, TRUE);
2. Set the window position/size
MoveWindow( GetDlgItem( hWnd, 1500 ),
p.x, p.y-2,
1+ rc.right - rc.left,
1+ 2 + rc.bottom - rc.top, TRUE );
Finally, contrast this against the original output from your code:
UPDATE:
Here's a snapshot of the appearance when the built-in label editing functionality is used (LVS_EDITLABELS style)
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);