Výběr listboxu v C++ Visual Studio

Výběr listboxu v C++ Visual Studio
« kdy: 03. 07. 2015, 21:50:07 »
Visual Studio C++ 2010

Mám listbox s názvy térénu. Informace o druzích indexů a názvů jsou uloženy v objektu esdata.terrains a ve struktuře, která se zpřístupnňuje přes pointer tn. tn->cnst obsahuje index položky, která se zrovna zpracovává (index=id).

Problém je ten že funkce funguje jen když listbox není uspořádaný pomocí LBS_SORT.

Tyto funkce jsou v programu původní:

Kód: [Vybrat]
LinkListBox_Fill(GetDlgItem(dialog, IDC_TR_ID), esdata.terrains.head());

Kód: [Vybrat]
void LinkListBox_Fill(HWND listbox, Link *list)
{
for (; list; list = list->next())
LinkListBox_Add(listbox, list);
}
LRESULT LinkListBox_Add(HWND listbox, const Link *link)
{
// Fill value into the list box:
WPARAM index = List_AddStringW(listbox, link->name());
// With AOK link->id() returns result qual to index
// Associate index with the list box:
List_SetItemData(listbox, index, link);
return index;
}

Když položky nejsou uspořádané a jsou správně zobrazené, tak když změním hodnotu x,y v edit boxu, pak
správný terén je vybrán.

Kontroloval jsem to a dostal jsem tyto hodnoty:

Když je index 5 (tn->cnst == 5) je asociován s názvem "leaves" a index 10 je asociován s "forest". index 0 je "grass 1".
To je vše OK.


Problém nastává když se snažím položky uspořádak, pak se nevybere správná položka.
Vytvořil jsem novou funkci na kontrolu hodnot:
Kód: [Vybrat]
LRESULT LinkListBox_Add_ByID(HWND listbox, const Link *link)
{
WPARAM indexByID = (WPARAM) link->id();
LPCWSTR name = link->name();
WPARAM index = List_AddStringW(listbox, name);
List_SetItemData(listbox, indexByID, link);
return indexByID;
}

Do kukátka přidám name, index a indexByID a když to ladím tak dostanu nesprávný index, ale indexByID je správně.

takže když indexByID == 5 pak name == "leaves", když indexByID == 10 pak name == "forest" ...
A toto je vpořádku. Proto teď používám tu novější funkci, ale přesto se nevybere ta správná položka.

Pokračuji v ladění. Zobrazí se okno, položky už jsou v listboxu vytvořené a seřazené abecedně.
Do edit boxu musím zadat x,y ... 1 a 1. Pak se zpracuje příslušná zpráva a zavolá metoda co updatuje údaje. A má se vybrat ta správná položka v listboxu:

Kód: [Vybrat]
SendDlgItemMessage(dialog, IDC_TR_ID, LB_SETCURSEL, tn->cnst, 0);
V tomto momentu tn->cnst čili hodnota indexu je správně: unsigned char 0x05 . To odpovádá "leaves".
Toto volání bylo provedeno z LoadMap() poté co jsem volal Map_UpdateCoord(dialog, id).

Následně se zobrazí okno a vidím že se zobrazila nesprávná položka. Ne "leaves" ale "dirt 1" což je myslím index 3.
Nemohu tedy přijít na to jak je to možné že se zobrazuje nesprávná položka. Protože jak jsem to ladil při vytváření toho seznamu, tak všechny hodnoty s tou novou funkcí jsou správné, takže snad se měl ten listbox vytvořit správně. Napadá někoho z vás kde by mohl být zakopaný pes?
« Poslední změna: 05. 07. 2015, 20:58:07 od Petr Krčmář »


xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #1 kdy: 03. 07. 2015, 22:49:34 »
Mírně jsem se ztratil v dotazu, ale  i tak se pokusím pomoci.

Windows úroveň nemá nic společného s c (c++) úrovní.

Jak to dělají windows:

AddString přidá položku (text) na windows úrovni a vrátí jeho index (pořadí) kam to (na windows úrovni) přidal. Není-li seřazeno, přidá se nakonec (a může odpovídat c++ úrovni pořadí, postupuje-li se vždy od začátku). Je-li seřazeno (LBS_SORT) windows si (interně) přehází pořadí, udělají mezeru (kusy posunou) a text vloží někam uprostřed. A vrátí index, ukazující někam doprostřed.

Následně, chci-li přidat data k položce v listboxu, musím použít ten (v tu chvíli aktuální vrácený) index z AddString a použít SetItemData s tím indexem. A přidat si svá data. Použití jiného indexu (vzatého z c++, byID) znamená přepsaná data u jiného textu.


Z toho plyne:
Pokud chci do windows dat listboxu vpašovat odkaz do C++ struktury (objektu), musím si do dat (poslaných via SetItemData) zadat odkaz na konkrétní položku listu (stačí-li), ne list jako celek. Nebo nějakou substrukturu list+pořadí (pozor na alokaci místa pak správné uvolnění). Nebo jen (c++) pořadí a odkaz na (c++) list získat jinak (třeba z dat parent okna, ... nebo statický odkaz v rámci celého programu, pokud je jediný ...)

Možná, že na translaci (zároveň link na položku i list jako celek) má visual studio nějaké své udělátko - to naneštěstí z rukávu nevysypu.


https://msdn.microsoft.com/en-us/library/2cekde96.aspx
https://msdn.microsoft.com/en-us/library/936147y4.aspx

xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #2 kdy: 03. 07. 2015, 23:38:19 »
Možná jsem měl začít ještě obecněji.

Windows si v listboxu dělají vlastní strukturu, vlastní seznam textů. A ten jejich index je doslova pořadí, zobrazení. Pokud je listbox řazený či volá-li se mezitím InsertString či delete a podobně, ten index se u již zapsaných řádek změní.
=> neexistuje něco jako ID. Je to jen pořadí z aktuálního zobrazení => může se u starých měnit.

Proto windows navíc dovolují přidat k textu i naše data (32bit DWORD_PTR). Pomocí SetItemData. Tam si můžeme poslat nějaký náš ID. Může to být cokoli, třeba pointer do paměti (na strukturu), naše pořadí, ...

----

P.S.: Dokud s listbox pracujeme na windows úrovni (to co je zobrazeno, ...), vraxí nám windows jejich v tu chvíli platný index (pořadí), tedy můžeme získat text i data. Pokud nemá (obecně) c++ seznam opakující se položky, dá se na to jít i hrubou silou a jako data (všech) listbox textů poslat odkaz na c++ seznam jako celek (to tuším děláte) a pokud už potřebujete pořadí z c++ seznamu, lze kek prohledat c++ úrovni (tam už strukturu známe) prohledat na vybraný string, který jsme dostali od windows.

Tím na c++ úrovni získáme jak odkaz na obecný c++ seznam, (ten z dat windows položky) tak i konkrétní položku.

https://msdn.microsoft.com/en-us/library/x6kt91ff.aspx
https://msdn.microsoft.com/en-us/library/xsyk39cx.aspx

xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #3 kdy: 04. 07. 2015, 04:57:35 »
A pro opačný případ, když potřebujeme najít určitý windows listbox záznam (resp. jeho aktuální index) lze např.:

- Pokud jsou texty různé (začínají jinak), použít LB_FINDSTRING
-- Možná bude stačit i jen LB_SELECTSTRING


- Do dat (SetItemData) si u každého textu ukládat náš unikátní index (např. const pointer, pevně dané pořadí v c++ const seznamu,  ...) a pak, když hledáme, postupně ručně projíždět pomocí LB_GETITEMDATA jednotlivé záznamy listboxu a porovnávat, až najdeme ten co hledáme.


Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #4 kdy: 04. 07. 2015, 10:46:27 »
Díky moc za vysvětlení, tak krásně vysvětlené jsem to nikde nenašel. Na msdn jsem to vůbec nepochopil, oni sice píšou že

LB_SETITEMDATA message

"Sets a value associated with the specified item in a list box.
Parameters"

Ale co ta hodnota znamená a jak to funguje jsem tam nenašel.

wParam

    Specifies the zero-based index of the item. If this value is -1, the

 lParam value applies to all items in the list box.

Těmi daty tedy myslíš lParam předpokládám?

V té původní funkci která je v programu který bych rád změnil je tento kód:

Kód: [Vybrat]
LRESULT LinkListBox_Add(HWND listbox, const Link *link)
{
WPARAM index = List_AddStringW(listbox, link->name());
List_SetItemData(listbox, index, link);
return index;
}

Ten je tedy podle toho jak to říkáš, že to vrací index té položky kde se nachází v seznamu a přiděluje to tomu linku. Nechápu však proč přestane vybírat správnou položku...

Když se znovu podívám na to co dělám a co je v LB_SETCURSEL
SendDlgItemMessage(dialog, IDC_TR_ID, LB_SETCURSEL, tn->cnst, 0);

Píše se:
"wParam - Specifies the zero-based index of the string that is selected."

Takže já tam neposílám index, ale data. To tedy asi znamená, že ve funkci
LinkListBox_Add bych měl uložit ten index do nějakého objektu abych to mohl později vybrat.


Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #5 kdy: 04. 07. 2015, 12:26:29 »
Tak ukládám ty indexy a zjišťuju, že ta funkce LinkListBox_Add nemá ten index jedinečný. Jak je ten seznam uspořádaný tak to nevrací např.
0,3,1,4,5,2, ale např. 0,1,0,0,3,2,3,0,2 apod, prostě jsou tam duplicity. To je normální? Já potřebuju jedinečné číslo. Možná to celé dělám špatně, měl bych nejdříve vložit ty stringy a až se to uspořádá celé to projet a nastavit tomu ty indexy?
Domnívám se že vysvětlení toho proč ty indexy se pořád mění a uloží se duplicitní je v tom že jak se přesouvá jedna a ta samá položka z místa na místo když tam přibývají jiné položky tak se mění jeho index.

Kód: [Vybrat]
int terrainIndecies[MAX_TERRAINS]; // needed to select item in sorted list/combo
int * pTerrainIndecies = terrainIndecies;
LinkListBox_Fill_keepAssociations(GetDlgItem(dialog, IDC_TR_ID),
esdata.terrains.head(), pTerrainIndecies);

Kód: [Vybrat]
int LinkListBox_Fill_keepAssociations(HWND listbox, Link *list, int * associations)
{
int index;
for (; list; list = list->next()){
index = LinkListBox_Add(listbox, list);
associations[list->id()] = index; // save for purposes of ordered list to select correct item
}
return *associations;
}

LRESULT LinkListBox_Add(HWND listbox, const Link *link)
{
WPARAM index = List_AddStringW(listbox, link->name());
LPCWSTR name = link->name();
int ID = link->id();
List_SetItemData(listbox, index, link);
return index;
}

cycle 1:
ID: 0
index: 0
name: Grass 1

cycle 2:
index: 1
ID: 1
name: Water, Shallow

cycle 3:
index: 0 (PROČ???)
ID: 2
name: Beach

cycle 4:
index: 1 (PROČ???)
ID: 3
name: Dirt 3

cycle 5:
index: 3 (PROČ???)
ID: 4
name: Shallows

cycle 6
index: 3 (PROČ???)
ID: 5
name: Leaves


Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #6 kdy: 04. 07. 2015, 13:40:43 »
Tak jsem si ještě jednou přečetl ty zbývající odpovědi a už tedy chápu proč se mění ten index.
Stejně ale ještě nechápu jak to celé vyřešit.

Měl bych místo link:
Kód: [Vybrat]
LRESULT LinkListBox_Add(HWND listbox, const Link *link)
{
WPARAM index = List_AddStringW(listbox, link->name());
LPCWSTR name = link->name();
int ID = link->id();
List_SetItemData(listbox, index, link);
return index;
}

zadat ID?
Kód: [Vybrat]
LRESULT LinkListBox_Add(HWND listbox, const Link *link)
{
WPARAM index = List_AddStringW(listbox, link->name());
LPCWSTR name = link->name();
int ID = link->id();
List_SetItemData(listbox, index, ID);
return index;
}

xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #7 kdy: 04. 07. 2015, 20:40:29 »
Také netuším, jak to celé vyřešit, protože přesně nevím, co je cílem. Tím úplným cílem. Střílím naslepo:

-----

Pokud potřebuješ, aby po nějaké vnější akci programu byl nastaven jiný text listboxu (a znáš jaký text a text je unikátní), použij LB_SELECTSTRING a je hotovo. Nepotřebuješ žádné indexy, windows si to dohledá sám.

Pokud potřebuješ zpětně zjistit co je vybráno (došlo-li ke změně), můžeš jednu po jedné prohledávat c++ seznam a porovnávat s textem text, který Ti vrátí LB_GETTEXT (třeba po zjištění  LB_GETCURSEL, použité jako index do LB_GETTEXT), pro aktuální vybranou položku.

------

Nebo jinak. Do widnows dat asociovaných s textem (setitemdata) ukládat ID pořadí z c++ seznamu. To má nevýhodu, že někde bokem ještě musíš mít odkaz na ten c++ list jako celek. Ale pokud máš, tak:

Potřebuješ-li zobrazit text podle c++ ID:
- hrubou silou (smyčkou) projíždět  postupně projíždět všechny windows záznamy listboxu a volat LB_GETITEMDATA a až najdeš shodu dat (do tech sis ukládal to ID) s hledaným ID, pak index smyčky určuje index windows položky, se kterou chceš pracovat. Třeba ji vybrat.

Opačně už je to jednoduché. Např. LB_GETCURSEL a následné LB_ GETITEMDATA Ti rovnou vrátí data, tedy ID (c++ záznamu).

----

Ale nedivil bych se, kdybychom vymýšleli Ameriku. Pravděpodobně už na něco takového bude mít VC nástavbu. Já netuším (pohybuji se na té nižší úrovni).



Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #8 kdy: 04. 07. 2015, 21:53:13 »
Všiml jsem si že v tom programu je nějaká funkce ListBox_Find, ale je to v jiném modulu a v jiném souboru tak jsem si toho dříve nevšiml:

Kód: [Vybrat]
SetDlgItemInt(dialog, IDC_U_CONST, type->id(), FALSE);
current_index = ListBox_Find(unitbox, type);

Kód: [Vybrat]
unsigned ListBox_Find(HWND unitbox, const void *type)
{
int index;
index = SendMessageW(unitbox, LB_GETCOUNT, 0, 0);

while (index--)
{
if (List_GetItemData_cPtr(unitbox, index) == type)
return index;
}

return UINT_MAX;
}
inline const void * List_GetItemData_cPtr(HWND control, WPARAM index)
{
LRESULT data = SendMessageW(control, LB_GETITEMDATA, index, 0);
return (data != LB_ERR) ?
reinterpret_cast<const void *>(data) : NULL;
}

Nejspíš to bylo určeno k tomu co chci. On tam totiž už jeden listbox uspořádaný existuje a výběr položky tam je taky. Nevím proč mě to nenapadlo dříve se tam podívat jak se to řeší v tom druhém modulu.
Asi je to tím je v tom prvním modulu to bylo odfláknuté a uspořádání autor prostě neřešil.

Teď mi stačí toto:
Kód: [Vybrat]
if ( tn->cnst != 0xff ){
link = esdata.terrains.getById(tn->cnst);
name = link->name();
SendDlgItemMessageW(dialog, IDC_U_TR_TERRAIN, CB_SELECTSTRING, -1, (LPARAM) name );
}
Spouštím u funkce Units_Load(HWND dialog) (druhý modul, první modul se týkal terénu).

Když tak zítra zkusím tu druhou funkci.

Ještě řeším jeden problém. Tomu poslednímu kódu co jsem uvedl předchází:
Kód: [Vybrat]
SetDlgItemFloat(dialog, IDC_U_X, u->x);
SetDlgItemFloat(dialog, IDC_U_Y, u->y);
což nastaví edit boxy x,y na hodnoty x,y a teprve potom mohu provést ten poslední kód. Problém je však v tom, že to nezareaguje hned, ale změna která měla být provedena na aktuální položce se projeví až u položky následující.

xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #9 kdy: 04. 07. 2015, 22:32:29 »
SetDlgItemFloat není standardní funkce. Ani pro windows a tuším ani pro MFC. Snad to zpoždění nedělá ona.
Nicméně předpokládám, že dělá nějakou translaci na SetDlgItemText (možná za použití locale pro desetinnou tečku/čárku).

Zpoždění netuším, ale mohlo by mít souvislost s message loop (třeba že se text nastaví, ale nezobrazí, dokud neprojde WM_PAINT a podobně). Navíc modální dialogy mívají své vlastní zpracování smyčky, takže záleží, kdy je voláno.

---

Zkus za každým SetDlgItemFloat zavolat něco jako PumpMessage(). Ale pozor, znamená to, že se mezitím vyřídí i jiné ostatní messages, tak ať se to nezacyklí, není-li to programem očekáváno.

xxxxx

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #10 kdy: 04. 07. 2015, 23:03:20 »
Případně listboxu direktně poslat (SendMessage) WM_UPDATE. Resp. volat UpdateWindow, to je tuším totéž.
Výhoda je, že takové volání by mělo jít přímo. Mimo messageLoop.

V nouzi zkusit WM_UPDATE na celý dialog.

----

Nicméně i tak pozor. Tuším, že pokud se update pošle na celý dialog a přitom uživatel právě něco píše do dialogu, může to mít za následek změnu pod rukama. Přeba posun kurzoru na 0, změny výběru textu pokud právě dělá drag, zavření otevřeného okna kalendáře a podobně. Dle verze.

Re:Problém s výběrem listboxu v C++ Visual Studio
« Odpověď #11 kdy: 05. 07. 2015, 11:04:06 »
Z manuálu:
Citace
The SendDlgItemMessage function does not return until the message has been processed.

Using SendDlgItemMessage is identical to retrieving a handle to the specified control and calling the SendMessage function.

Je třeba počkat až se daná zpráva zpracuje než mohu pokračovat. Kdyby se nepočkalo mohlo by to mít na něco vliv v případě, že se akce provede moc rychle (například díky makru, programu na urychlení/zautomatizování práce s jiným programem).