Assigning group ID for CListCtrl C++ - c++

I'm currently using a derived class of CListCtrl to build my application upon. I'm trying to add an item to the list, and immediately assign it to a group:
// int row, int grp_id, CString header; - all initialized previously
int ind = m_list.InsertItem(row, header);
VERIFY(m_list.SetRowGroupId(row, grp_id));
Here's an implementation of SetRowGroupID():
BOOL CGridListCtrlGroups::SetRowGroupId(int nRow, int nGroupId)
{
//OBS! Rows not assigned to a group will not show in group-view
LVITEM lvItem = { 0 };
lvItem.mask = LVIF_GROUPID;
lvItem.iItem = nRow;
lvItem.iSubItem = 0;
lvItem.iGroupId = nGroupId;
return SetItem(&lvItem);
}
Nothing too fancy here.
However, the code is asserting false for VERIFY().
While I was searching through MSDN documents to find out why, it seems that the flag I want to use as a mask isn't available for this purpose (refer to Remarks).
Am I not able to change the group ID this way? For the record, I've also tried to use MoveItemToGroup(), which gave me the same result (that is, the item doesn't show up while group view is enabled).

I cannot say why updating a member's group ID does not work directly.
However, setting .iGroupId = I_GROUPIDCALLBACK triggers the message LVN_GETDISPINFO and in its handler you can then assign the group ID.
Outline:
BEGIN_MESSAGE_MAP(CGridListCtrlGroups, CListCtrl)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)
END_MESSAGE_MAP()
void CGridListCtrlGroups::CGridListCtrlGroups(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVDISPINFO* dispInfo = LPNMLVDISPINFOW(pNMHDR);
LVITEM* item = &dispInfo->item;
if (item->mask & LVIF_GROUPID) {
// assign group id
item->iGroupId = ...;
}
*pResult = 0;
}
BOOL CGridListCtrlGroups::RequeryRowGroupId(int nRow)
{
LVITEM lvi = {
.mask = LVIF_GROUPID,
.iItem = nRow,
.iGroupId = I_GROUPIDCALLBACK
};
return SetItem(&lvi);
}

Related

I want to use the win32 api to create a printer dialog box but DEVMODE unable to set default

I can successfully create the dialog box, and get the data of the user operation to my structure, but I can't set the value when it is opened, and it is the default printer and paper of windows every time.
For example, the dmDeviceName member in my DEVMODE is "A", but it is still the default printer when I open the dialog box. I read the documentation on MSDN, the requirement is a global variable, and I can’t open it correctly even if I copy it after applying. Even if I confirm that the printer name of the PRINTDLGA structure passed in PrintDlgA() is set by me during debugging, it still doesn't work.
Below is my function. The functions after PrintDlgA(&printDlg) can work correctly, but DEVMODEA has no effect when initializing PRINTDLGA when it is opened.
inline int OpenPrintSetupDlg(DEVMODEA n_in_info, CPrintInfo* n_out_info, BOOL is_inly_set) {
int result = PRINT_CN;
PRINTDLGA printDlg = { 0 };
printDlg.lStructSize = sizeof(PRINTDLGA);
printDlg.Flags = is_inly_set ? PD_RETURNDC | PD_PRINTSETUP : PD_RETURNDC;
HGLOBAL pdevmode = GlobalAlloc(GMEM_ZEROINIT, sizeof(DEVMODEA));
if (!pdevmode)
{
return result;
}
printDlg.hDevMode = (PDEVMODEA)GlobalLock(pdevmode);
if (!printDlg.hDevMode)
{
GlobalFree(pdevmode);
return result;
}
memcpy(printDlg.hDevMode, &n_in_info, sizeof(DEVMODEA));
//debug_put((char*)((DEVMODEA*)printDlg.hDevMode)->dmDeviceName, (char*)((DEVMODEA*)pdevmode)->dmDeviceName, (char*)n_in_info.dmDeviceName);
//GlobalUnlock(printDlg.hDevMode);
if (PrintDlgA(&printDlg))
{
if (n_out_info)
{
if (printDlg.hDevNames)
{
LPDEVNAMES lpDevNames = (LPDEVNAMES)GlobalLock(printDlg.hDevNames);
LPSTR lpszDriver = (LPSTR)lpDevNames + lpDevNames->wDeviceOffset;
n_out_info->m_DeviceName = CloneTextData(reinterpret_cast<char*>(lpszDriver));
}
if (printDlg.hDevNames) {
PDEVMODEA devmode = (PDEVMODEA)GlobalLock(printDlg.hDevMode);
n_out_info->set_info(devmode);
}
}
result = PRINT_OK;
}
if (printDlg.hDevMode)
{
GlobalFree(printDlg.hDevMode);
printDlg.hDevNames = nullptr;
}
if (printDlg.hDevNames)
{
GlobalFree(printDlg.hDevNames);
printDlg.hDevMode = nullptr;
}
return result;
}

Is it possible to add strings that differ by case to CMFCToolBarComboBoxButton?

MSDN appears to be wrong
MSDN
CMFCToolBarComboBoxButton::AddItem
...If the item text is already in the list box, the new data is stored with the existing item. The search for the item is case sensitive.
However AddItem calls into FindItem to prevent duplication
int CMFCToolBarComboBoxButton::FindItem(LPCTSTR lpszText) const
{
ENSURE(lpszText != NULL);
int iIndex = 0;
for (POSITION pos = m_lstItems.GetHeadPosition(); pos != NULL; iIndex++)
{
if (m_lstItems.GetNext(pos).CompareNoCase(lpszText) == 0) <-- gets rejected as duplicate
{
return iIndex;
FindItem isn't virtual so I can't override it.
Any workarounds?

C++ Slow fetching of Recordset rows

I've got an issue with getting the rows in the Recordset as it is really slow.
We've got an virtual ListCtrl where the data is retrieved and set in the "OnGetdispinfo" method.
This is pretty fast (~2 Seconds for 300k rows on localhost) however if the connection is slow the GUI becomes unrepsonsive and completly unusable until the job is finished.
So I've tried to do the Sql stuff in a different thread and updating the list once all data is fetched.
The issue with the unresponsive GUI is solved with that, but the time it takes to get all the data jumped from 2 seconds to several minutes.
Even if I dont do anything but loop through the rows (just calling MoveNext() in the loop until EOF is reached) it will still take over a minute to complete.
How do I resolve the issue with the freezing GUI without completly destroying the performance here?
I've included the relevant code below
m_pRecordset is a normal Recordset
Old:
void KundenListControlSQLCommand::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pRecordset->IsBOF())
{
*pResult = 0;
return;
}
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &(pDispInfo)->item;
if (pItem->mask & LVIF_TEXT)
{
CString strData;
m_pRecordset->SetAbsolutePosition(pItem->iItem + 1);
if (getStatusRow() != pItem->iSubItem)
{
m_pRecordset->GetFieldValue(short(pItem->iSubItem), strData);
}
::lstrcpy(pItem->pszText, strData);
}
if (pItem->mask & LVIF_IMAGE)
{
int const nIndex = this->GetParent()->SendMessage(OT_VLC_ONGETIMAGEINDEX, pItem->iItem, 0);
if (0 != nIndex)
{
pItem->iImage = nIndex - 1;
}
}
*pResult = 0;
}
void KundenListControlSQLCommand::loadAndDisplayData()
{
ASSERT(!m_strSQLCommand.IsEmpty());
CWaitCursor wc;
try
{
if (!m_pDatabase->IsOpen())
{
CString strSQL = m_pDatabase->getDatabaseInfo().getConnectString();
m_pDatabase->OpenEx(strSQL);
}
// RecordCount ermitteln
m_nRecordCount = m_pRecordset->selectCount(_T("*"), m_strSQLCommand);
if (m_pRecordset->IsOpen())
m_pRecordset->Close();
m_pRecordset->Open(Recordset::snapshot, m_strSQLCommand + m_strSortOrder,
Recordset::executeDirect | Recordset::noDirtyFieldCheck |
Recordset::readOnly | Recordset::useBookmarks);
SetItemCountEx(m_nRecordCount);
}
catch (CDBException* e)
{
e->ReportError();
e->Delete();
}
}
New:
void KundenListControlSQLCommand::loadAndDisplayData()
{
ASSERT(!m_strSQLCommand.IsEmpty());
CWaitCursor wc;
try
{
if (!m_pDatabase->IsOpen())
{
CString strSQL = m_pDatabase->getDatabaseInfo().getConnectString();
m_pDatabase->OpenEx(strSQL);
}
// RecordCount ermitteln
m_nRecordCount = m_pRecordset->selectCount(_T("*"), m_strSQLCommand);
if (m_pRecordset->IsOpen())
m_pRecordset->Close();
m_pRecordset->Open(Recordset::dynaset, m_strSQLCommand + m_strSortOrder,
Recordset::executeDirect | Recordset::noDirtyFieldCheck |
Recordset::readOnly | Recordset::useBookmarks);
m_vResult.clear();
m_vResult.reserve(m_nRecordCount);
int nFieldCount = m_pRecordset->GetODBCFieldCount();
CString strData;
while (!m_pRecordset->IsEOF())
{
for (auto i = 0; i < nFieldCount; i++)
{
m_pRecordset->GetFieldValue(short(i), strData);
m_vResult.push_back(std::move(strData));
}
if (m_bAbort)
{
m_bAbort = false;
return;
}
m_pRecordset->MoveNext();
}
GetParent()->SendMessage(OT_VLC_ON_LIST_DONE, NULL, NULL);
}
catch (CDBException* e)
{
e->ReportError();
e->Delete();
}
}
void KundenListControlSQLCommand::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pRecordset->IsBOF())
{
*pResult = 0;
return;
}
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &(pDispInfo)->item;
UINT nItem = (pItem->iItem * m_pRecordset->GetODBCFieldCount()) + pItem->iSubItem;
if (pItem->mask & LVIF_TEXT && m_vResult.size() >= nItem)
{
::lstrcpy(pItem->pszText, std::move(m_vResult.at(nItem)));
}
if (pItem->mask & LVIF_IMAGE)
{
int const nIndex = this->GetParent()->SendMessage(OT_VLC_ONGETIMAGEINDEX, pItem->iItem, 0);
if (0 != nIndex)
{
pItem->iImage = nIndex - 1;
}
}
*pResult = 0;
}``
As I can see in your code, you read the data and place them into the vector. In such a setting, I think you don't really need a dynaset recordset, which according to the documentation is "A recordset with bi-directional scrolling". It fetches data row-by-row, which may be what makes the process slow. Also, "changes made by other users to the data values are visible following a fetch operation", but I think this is not of critical importance in this case. It would be mostly useful for displaying more "live" data, that are updated often.
Instead, a snapshot, or even forwardOnly recordset would suffice and would be faster. You can also experiment with the CRecordset::useMultiRowFetch option. The documentation says it's faster. It requires some changes to your code (moving next etc). Take a look here: Recordset: Fetching Records in Bulk (ODBC).
An alternative, radically different implementation would be to use bookmarks instead. Loading would be a lot faster, but scrolling somewhat sluggish, as you will have to fetch data in the OnGetdispinfo() function.
Finally a tip, if you are using the MS-SQL server, check the native driver, if you haven't already, many on the i-net claim that it's considerably faster.
I don't know much about ODBC, but suspect that there are better way to get bulk data.
Regardless, you do a lot of unnecessary copying of your vectors. Two easy fixes:
Right after m_vResult.clear();, resize your m_vResult to the number of records.
Instead of m_vResult.push_back(vResult); do m_vResult.push_back(std::move(vResult));, as you don't need your vResult after that.
Another solution is to do a cache list, handling LVN_ODCACHEHINT notification (this example is for CListView but you can adapt it on your CListCtrl:
// header.h
class CYourListView : public CListView
{
// ...
afx_msg void OnLvnOdcachehint(NMHDR* pNMHDR, LRESULT *pResult);
};
and implementation:
// YourListView.cpp
// ...
ON_NOTIFY_REFLECT(LVN_ODCACHEHINT, &CYourListView::OnLvnOdcachehint)
END_MESSAGE_MAP()
void CYourListView::OnLvnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLVCACHEHINT pCacheHint = reinterpret_cast<LPNMLVCACHEHINT>(pNMHDR);
const DWORD dwTo = pCacheHint->iTo;
const DWORD dwFetched = m_vResult.size();
if (dwTo >= dwFetched) // new rows must be fetched
{
const DWORD dwColCount = m_pRecordset->GetColumnCount();
m_vResult.resize(dwTo + 1);
for (DWORD dwRow = dwFetched; dwRow <= dwTo; ++dwRow)
{
CDBRecord* pRecord = new CDBRecord;
pRecord->SetSize(dwColCount);
for (DWORD dwCol = 1; dwCol <= dwColCount; dwCol++)
{
CDBValue* pDBValue = new CDBValue(m_pRecordset, dwCol);
pRecord->SetAt(dwCol - 1, pDBValue);
}
m_vResult.emplace(m_vResult.begin() + dwRow, pRecord);
m_pRecordset->MoveNext();
}
}
*pResult = 0;
}
might be need to adjust some variables / values with your certain situation.

Sub item in ListView not showing up using Windows API in C++

I have two functions to init the listview and one to add entries to the list view. I created a struct that holds the column information.
struct LISTVIEW_COLUMN
{
const TCHAR * Title;
int Width;
};
This is how I am creating the column struct
static struct LISTVIEW_COLUMN lvNames[] =
{
{ TEXT("Name"), 150 },
{ TEXT("Last"), 125 }
};
Then I have the functions defined as follows.
void InitListView(HWND hListView, LISTVIEW_COLUMN * lvColumn, int nCols)
{
ListView_SetExtendedListViewStyle(hListView, LVS_EX_FULLROWSELECT);
// Create the columns
for (int col = 0; col < nCols; col++) {
LVCOLUMN lvc;
lvc.pszText = (TCHAR *)lvColumn[col].Title;
lvc.cx = lvColumn[col].Width;
lvc.mask = LVCF_TEXT | LVCF_WIDTH;
ListView_InsertColumn(hListView, col, &lvc);
}
}
void NetPCAddListViewRow(HWND hListView, TCHAR * name, TCHAR * lastName)
{
LVITEM lvItem;
// Add item (name)
lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
lvItem.iItem = 0;
lvItem.iSubItem = 0;
lvItem.iImage = 0;
lvItem.pszText = (TCHAR *)name;
lvItem.iItem = ListView_InsertItem(hListView, &lvItem);
// Just using "testing" right now to see it as a subitem
lvItem.iSubItem += 1;
lvItem.pszText = _T("testing");
ListView_SetItem(hListView, &lvItem);
}
The columns show up just fine and the first column "name" gets added to but I cant get anything to show up as a subitem in that column? Thanks for any help!
If you look at the How to Add List-View Columns example on MSDN you will see that they set the iSubItem member and the LVCF_SUBITEM flag when they insert the columns. This establishes the relationship between sub-items and columns (columns can be re-ordered by the user).
I would also recommend that you reset lvItem.mask before adding sub-items because they only support a subset of the flags.

GetMenuItemInfo does not set fType with MIIM_TYPE

I have been working with winapi just a little bit, making a project with owner draw on menus. When I called GetMenuItemInfo, it sets the text of the menu item, but not the fType UINT variable flags.
Currently I have declared:
MenuItem->fMask = MIIM_TYPE
And MSDN says:
MIIM_TYPE Retrieves or sets the fType and dwTypeData members.
I don't know If I got confused with the MIIM_TYPE flag.
Here is my code:
void SetOwnerDrawMenu(HMENU * menu)
{
MENUIF * menu_item_information;
HMENU sub_menu_ocational;
UINT uId_menuitem;
int nMenuCountItems = GetMenuItemCount(*menu);
MENUITEMINFO * MenuItem = (MENUITEMINFO*)malloc(sizeof(MENUITEMINFO));
for(int i=0;i<nMenuCountItems;i++)
{
menu_item_information = (MENUIF*)malloc(sizeof(MENUIF));
menu_item_information->isSeparator=false;
menu_item_information->max_width=0;
sub_menu_ocational = 0;
uId_menuitem = GetMenuItemID(*menu,i);
memset(&MenuItem,0,sizeof(MenuItem));
MenuItem = (MENUITEMINFO*)malloc(sizeof(MENUITEMINFO));
MenuItem->cbSize = sizeof(MenuItem);
MenuItem->fMask = MIIM_TYPE;
MenuItem->cch = MAX_ODM_CCH;
MenuItem->dwTypeData = menu_item_information->szItemText;
GetMenuItemInfo(*menu,uId_menuitem,FALSE,MenuItem);
UINT final_flags = MF_BYPOSITION | MF_OWNERDRAW;
if( ( MFT_SEPARATOR & MenuItem->fType ) == MFT_SEPARATOR )
{
final_flags |= MF_SEPARATOR;
menu_item_information->isSeparator = true;
}
else
{
// Not important stuff
}
sub_menu_ocational = GetSubMenu(*menu,i);
if(sub_menu_ocational!=NULL)
{
ModifyMenu(*menu,i,final_flags,0,(LPCTSTR)menu_item_information);
// We got a submenu, repeat this operation
SetOwnerDrawMenu(&sub_menu_ocational);
}
else
{
ModifyMenu(*menu,i,final_flags,0,(LPCTSTR)menu_item_information);
}
}
}
I am inserting the menus with the InsertMenu function:
InsertMenu(tid_cmenu,0,MF_BYPOSITION | MF_SEPARATOR,0,NULL);
InsertMenu(tid_cmenu,0, MF_BYPOSITION | MF_STRING, TID_EXIT, "Exit");
Exactly, why the GetMenuItemInfo is not retriving the fType?
If you were checking the return code from GetMenuItemInfo you would see that it is failing. Your error is in this line:
MenuItem->cbSize = sizeof(MenuItem);
The MENUITEMINFO::cbSize member is supposed to be set to the size of a MENUITEMINFO structure, but you are setting it to the size of a MENUITEMINFO* pointer (i.e. 4 or 8 bytes, depending on the platform).
Change your code to:
MenuItem->cbSize = sizeof(MENUITEMINFO);
Also, your code is allocating MenuItem outside the loop, as well as once per-iteration inside the loop, so you are leaking memory.
Ok. The problem is not syntax or memory size errors.
It is more like 'logic' error and a silly mistake.
The ModifyMenu was changing all the menu items and
setting the fType of each one to NULL or setting the MF_SEPARATOR to all of the items.
That happened because the fourth argument of the ModifyMenu should be the ID of the menu item, I was declaring it as 0.
I changed that argument to the real ID of the menu Item using the GetMenuItemID return value inside the uId_menuitem variable and passing it to the fourth argument of ModifyMenu. That fixed the problem.
Thanks!