MFC ListView Change Item Image - c++

I have added items to ListControl, they have images. Now I want to change them, I tried to do GetItem and SetItem, but I was not able. At least I don't know how to get an Item I want. How I can change Image of an item in ListView?
Thanks
P.S.
I've managed to solve it. Here is solution:
This is how to loop
LVITEMW pitem;
ZeroMemory(&pitem, sizeof(pitem));
pitem.mask = LVIF_TEXT | LVIF_IMAGE;
pitem.iItem = <SET INDEX OF YOUR ITEMS HERE, YOU CAN LOOP HERE>;
pitem.iSubItem = 0;
pitem.pszText = new wchar_t[256];
pitem.cchTextMax = 255;
mlist.GetItem(&pitem);
And after selecting an item, you can change it's image like this:
pitem.iImage = newindex;
mlist.SetItem(&pitem);

Using CListCtrl::SetItem is right. You have to set the nMask parameter to LVIF_IMAGE and provide the index of the image in der image-list in the iImage parameter.

The solution highlighted in the first post was not working for me. After a short look to Microsoft Documentation, the signature of the SetItem function is:
BOOL SetItem(const LVITEM* pItem);
pItem should be a pointer to a LVITEM which is const, which is not really the case in this situation...
However, the next solution is working for me:
// Let's say my CListCtrl is named m_listCtrl
// Loop on items of CListCtrl
for( int i = 0; i < m_listCtrl.GetItemCount(); i++ )
{
// And then you define a new image with the index iImage for the item i
m_listCtrl.SetItem(i, 0, LVIF_IMAGE, NULL,
iImage, 0, 0, 0);
}

Related

MFC CListCtrl - how to move item text to the left

I'm trying to implement single-column CListCtrl (or CMFCListCtrl, doesn't matter) in a way, that some rows might have checkboxes and some might not (I don't want to use neither CListBox, nor CCheckListBox, because in the future I'm planning to use multiple columns). I'm using LVS_EX_CHECKBOXES style, but that forces every item to have a checkbox. Then I manually delete the checkbox with a custom draw handler, but then I'm having trouble moving the item's text to the left side so that it takes place of the erased checkbox.
This is what my list control looks like:
But I need it to look like this (item2 is aligned to the left border, taking place of the erased checkbox):
I create my list control dynamically like this:
list->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_NOCOLUMNHEADER
rect, this, SOME_ID);
list->SetExtendedStyle(list->GetExtendedStyle() | LVS_EX_CHECKBOXES);
And my custom draw handler function looks like this:
I create my list control dynamically like this:
void MyCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = CDRF_DODEFAULT;
LPNMLVCUSTOMDRAW lpn = (LPNMLVCUSTOMDRAW)pNMHDR;
if (CDDS_PREPAINT == lpn->nmcd.dwDrawStage)
{
*pResult = CDRF_NOTIFYITEMDRAW; // get notification for every row (item)
}
else if (CDDS_ITEMPREPAINT == lpn->nmcd.dwDrawStage)
{
int row = lpn->nmcd.dwItemSpec;
if (row == 1) { // we are in the first row (item2)
lpn->nmcd.rc.left -= 20; // doesn't do anything
lpn->rcText.left -= 20; // doesn't do anything
// this erases checkbox from the current row
SetItemState(row, INDEXTOSTATEIMAGEMASK(0), LVIS_STATEIMAGEMASK);
}
}
}
Is there any way to achieve the desired result? Am I doing it the right way, or is it better to use CListCtrl without the LVS_EX_CHECKBOXES and draw the checkboxes myself where I want to? If so, how? Thanks in advance.
You could try indentation with minus value (I didn't tried though):
LV_ITEM lvItem;
lvItem.iItem = nYourItem;
lvItem.iSubItem = 0;
lvItem.mask = LVIF_INDENT;
lvItem.iIndent = -1;
VERIFY(SetItem(&lvItem));
If it would be an option for you, why don't you just leave it as it is?
The first version looks much more uncluttered, anyway.
The second version breaks guidance of the eyes.

Iterating over ListView Items win32 api

I currently have a handle to my Listview via HWND lv = GetDlgItem(hDlg, MY_LISTVIEW)
and its currently populated with items using ListView_SetItemText();
I want to update each item in that listview based on data that has been updated externally. How would I iterate over each listview item given my handle?
ListViews use a 0-based index to identify items, so to iterate over the items it's simply a matter of getting the total and then running a loop that counts from 0. For example,
int iNumItems = ListView_GetItemCount(lv);
for (int iIndex = 0; iIndex < iNumItems; ++iIndex)
{
// update this item
ListView_SetItemText(lv, iIndex, 0, ...);
}

How can i find treeview node by which label equals string variable?

I would like to start by saying thanks to everyone who takes some time to view this thread and try to help.
I have searched the Internet, and couldn't find an example of selecting tree view node that has label text as same as text of a string variable.
On MSDN I have found message TVM_GETISEARCHSTRING but I don't know if it can be used to solve my problem. Even if it can, I still don't know how to use it.
I have a string variable that holds text from database.
When program loads, tree view should have a node with the same text selected.
Please help with some instructions or code snippets, since I have no clue how to even start coding this.
I work in MS Visual Studio Express 2008, on Windows XP, in C++, using pure WIN32 API.
That would be all, again I thank everyone who tries to help.Thank you very very much!
EDIT:
Both answers are good for me, but I don't know how to mark them both, it seems that on this site only one answer can be accepted.
I couldn't have just neglected all the work both of them invested to help me, so I write this in order to try to repay to the Jonathan at least by officially stating that his solution is acceptable for me too, it is just that Tim's solution suits my coding style better.I will also upvote both answers.
The treeview control does not provide an API to search for a label. You will have to manually traverse the items and compare them to your string.
If your treeview is more than one level deep you will have to decide how to traverse the items (either depth first or breadth first). In case there are multiple items with the same label these strategies may return different items.
An implementation might look something like this:
// Helper function to return the label of a treeview item
std::wstring GetItemText( HWND hwndTV, HTREEITEM htItem )
{
static const size_t maxLen = 128;
WCHAR buffer[ maxLen + 1 ];
TVITEMW tvi = { 0 };
tvi.hItem = htItem; // Treeview item to query
tvi.mask = TVIF_TEXT; // Request text only
tvi.cchTextMax = maxLen;
tvi.pszText = &buffer[ 0 ];
if ( TreeView_GetItem( hwndTV, &tvi ) )
{
return std::wstring( tvi.pszText );
}
else
{
return std::wstring();
}
}
This is where the actual traversal takes place. The function is called recursively until no more items can be searched or a match has been found. This implementation uses a case-sensitive comparison (wstring::operator==( const wstring& )). If you need a different predicate you will have to modify the implementation as you see fit.
HTREEITEM FindItemDepthFirstImpl( HWND hwndTV, HTREEITEM htStart, const std::wstring& itemText )
{
HTREEITEM htItemMatch = NULL;
HTREEITEM htItemCurrent = htStart;
// Iterate over items until there are no more items or we found a match
while ( htItemCurrent != NULL && htItemMatch == NULL )
{
if ( GetItemText( hwndTV, htItemCurrent ) == itemText )
{
htItemMatch = htItemCurrent;
}
else
{
// Traverse into child items
htItemMatch = FindItemDepthFirstImpl( hwndTV, TreeView_GetChild( hwndTV, htItemCurrent ), itemText );
}
htItemCurrent = TreeView_GetNextSibling( hwndTV, htItemCurrent );
}
return htItemMatch;
}
The following function wraps the recursion and passes the root element as the starting point. This is the function you would call in your code. It will return an HTREEITEM if one is found, NULL otherwise.
HTREEITEM FindItem( HWND hwndTV, const std::wstring& itemText )
{
HTREEITEM htiRoot = TreeView_GetRoot( hwndTV );
return FindItemDepthFirstImpl( hwndTV, htiRoot, itemText );
}
Unfortunately there is no documented way to search a treeview by item label.
The TVM_GETISEARCHSTRING message returns the search string that the user has typed into the tree (incremental search mode) but it doesn't trigger a search or let you supply your own search string.
The only way to do it is by manually iterating the tree nodes and comparing the labels yourself. Below is an example function, beware that it is recursive and will use about half a KB of stack for every child level.
HTREEITEM TreeView_FindLabel(HWND hWnd, HTREEITEM hItemParent, LPCWSTR pszLabel)
{
TVITEM tvi;
wchar_t wchLabel[256];
for (tvi.hItem = TreeView_GetChild(hWnd, hItemParent); tvi.hItem;
tvi.hItem = TreeView_GetNextSibling(hWnd, tvi.hItem))
{
tvi.mask = TVIF_TEXT | TVIF_CHILDREN;
tvi.pszText = wchLabel;
tvi.cchTextMax = _countof(wchLabel);
if (TreeView_GetItem(hWnd, &tvi))
{
if (_wcsicmp(tvi.pszText, pszLabel) == 0)
return tvi.hItem;
if (tvi.cChildren)
{
HTREEITEM hChildSearch = TreeView_FindLabel(hWnd, tvi.hItem, pszLabel);
if (hChildSearch) return hChildSearch;
}
}
}
return 0;
}
This is not a particularly fast way of searching the tree. If you need to do lots of searches you would be better using a std::map to keep track of the labels and tree items yourself. For example,
std::map<std::wstring, HTREEITEM> mapTreeItems;
// whenever you add an item
HTREEITEM hItem = ListView_InsertItem(...);
mapTreeItems[strLabel] = hItem;
Then you can lookup tree items by label using the map. You just have to remember to update the map and erase labels whenever an item is deleted from the tree.

When deleting item in Combobox the secong one pop-up MFC

I have combo box and delete button. I want to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item.
I tried several methods with indexes but even one wont help me.
there is my code:
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
}
How I can to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item?
I found a solution:
void CL2HamsterDlg::OnBnClickedButtonDelete(){
if(Validate()){
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
lookforfile();
int nIndex = pComboBox->GetCurSel();
if (nIndex == CB_ERR)
pComboBox->SetCurSel(0);
else{
pComboBox->SetEditSel(0, -1);
pComboBox->Clear();
}
}
LoadSave(false);
}else
AfxMessageBox(L"Please select or write correct name!");
}
the function look for file refreshes index
void CL2HamsterDlg::lookforfile()
{
Value.GetWindowText(str);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
GetCurrentDirectory(MAX_PATH,curWorkingDir);
_tcscat_s(curWorkingDir, MAX_PATH, _T("\\Save\\*.dat"));
BOOL bWorking = finder.FindFile(curWorkingDir);
while (bWorking){
bWorking = finder.FindNextFile();
if (!finder.IsDots())
pComboBox->AddString(finder.GetFileTitle());
}
GetDlgItem(IDC_COMBO_SAVE)->SetWindowText(str);
}
so, in this case you do not need to use ResetContent(). Provided you already know the currently selected Item in the combobox (I think somewhere along the track you would have used the line int iSel = pComboBox->GetCurSel();) you could use this code IN PLACE OF YOUR pComboBox->ResetContent();:
pComboBox->DeleteString(iSel);
if(iSel < pComboBox->GetCount())
pComboBox->SetCurSel(iSel);
else if(iSel > 0)
pComboBox->SetCurSel(iSel-1);
However, I think this will not be necessary. I think the item will move by itself. So, forget about the code above, just use this:
pComboBox->DeleteString(pComboBox->GetCurSel())

GTKmm - How to put a pixbuf in a treeview

I'm learning how to use GTKmm and I'm having a really hard time figuring out how to put an image into a treeview. I used Glade to create a treestore with 3 columns, one of which is a GdkPixbuf called store_pixbuf. I also created a treeview in glade, with a column that has both a pixbuf cell renderer called int_col_pict and a char array cell renderer. In my code, I have the usual MyColumns definition for the treestore like:
class MyModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
Gtk::TreeModelColumn<Glib::ustring> store_hostname;
Gtk::TreeModelColumn<Glib::ustring> store_intname;
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > store_pict;
MyModelColumns () { add(store_hostname); add(store_intname); add(store_pict);}
};
and use the following bit of code to populate it.
//Get a pointer to the treestore
Glib::RefPtr<Gtk::TreeStore> treestore = Glib::RefPtr<Gtk::TreeStore>::cast_static(builder->get_object("routerTreeStore"));
//make sure the pointer isn't bad
if(treestore){
MyModelColumns columns;
//populate the first column
Gtk::TreeRow row= *(treestore->append());
row[columns.store_hostname] = router->hostname;
//populate all children
for(int i=0; i<router->interfaces.size(); i++)
{
//append child row
Gtk::TreeRow child = *(treestore->append(row.children()));
//insert data into the row
child[columns.store_pict] = Gdk::Pixbuf::create_from_file("red_dot.png");
child[columns.store_intname] = router->interfaces[i].interfaceName;
}
}//if
I initially tried to use a stock image, but I could not figure out what function I was supposed to use, so then I tried to use Gdk::Pixbuf::create_from_file() (as you can see above), but at run time I get the following error:
Gtk-WARNING **: gtktreestore.c:765: Unable to convert from GdkPixbuf to gtkmm__GdkPixbuf
Click here to see what it looks like running. The image is supposed to go on the same line as the "FastEthernet..." lines
Does anyone know how I can solve this? Am I going about it completely wrong? Thanks for looking, every little bit of help is appreciated!
Your code looks correct. I just coded up a quick example and with gtkmm-2.4, I have no problems creating a column for a Glib::RefPtr
I couple of questions: what version of gtkmm are you using? Are you adding a column in the treeview for the Pixbuf?
I won't post my complete example but the relevant bits are:
in example.h
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_store_pict);}
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > m_col_store_pict;
};
ModelColumns m_Columns;
in example.cpp
//Create the Tree model:
m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);
//Fill the TreeView's model
Gtk::TreeModel::Row row = *(m_refTreeModel->append());
row[m_Columns.m_col_store_pict] = Gdk::Pixbuf::create_from_file("/usr/share/icons/gnome/22x22/apps/arts.png");
row = *(m_refTreeModel->append());
row[m_Columns.m_col_store_pict] = Gdk::Pixbuf::create_from_file("/usr/share/icons/gnome/22x22/apps/fonts.png");
row = *(m_refTreeModel->append());
row[m_Columns.m_col_store_pict] = Gdk::Pixbuf::create_from_file("/usr/share/icons/gnome/22x22/apps/access.png");
//Add the TreeView's view columns:
m_TreeView.append_column("Some Picture", m_Columns.m_col_store_pict);
Is that any help?
If all you want is a potentially different stock icon for each row, you can do it by setting an pixbuf renderer attribute. The idea is that the column contains a string stock-id, which the renderer displays as an icon.
// in your class declaration
struct columnsRecord : public Gtk::TreeModel::ColumnRecord {
...
Gtk::TreeModelColumn<std::string> stockID; // stock id name
...
} treeColumns;
Gtk::CellRendererPixBuf pixBufRenderer;
...
// setting up columns
int numcols = treeView.append_column("Icon Column", pixBufRenderer); // returns # cols after append
treeView.get_column(numcols-1)->add_attribute(pixBufRenderer,"stock-id",treeColumns.stockID)
...
// setting a row
std::string id = good_val ? Gtk::Stock::YES.id : Gtk::Stock::NO.id;
rowiter->set_value(columns.stockID,id);
Looks like it's necessary to explicitly set CellRenderer for pixbuf cells.
This code snippet displays CANCEL icon on each data row.
Gtk::CellRendererPixbuf *cross = Gtk::manage(new Gtk::CellRendererPixbuf());
cross->property_stock_id() = Gtk::StockID(Gtk::Stock::CANCEL).get_string();
cross->property_stock_size() = Gtk::ICON_SIZE_BUTTON;
[...]
int cols_count = m_TreeView.append_column("icons", *cross);
For displaying custom images you need to remove two lines setting stock_id add something like this below:
Gtk::TreeViewColumn* pColumn = m_TreeView.get_column(cols_count - 1);
if(pColumn) {
pColumn->add_attribute(cell->property_value(), columns.store_pict);
}