Disassembler

Artificial intelligence is no match for natural stupidity.
06prosince2011

C#: SelectedIndices v ListView s VirtualMode


Aneb jak mi z Microsoftu málem praskla žilka.

Jednoho krásného dne jsem programoval aplikaci. Jednalo se jednoduchý wake-on-LAN prográmek, který zároveň pomocí pingu zjišťuje stav vzdáleného stroje a z kontextového menu vám snadno umožní připojit se po PuTTY nebo Microsoftí vzdálené ploše. Možná jej sem jednou dám i ke stažení. Tento prográmek používá jako hlavní GUI komponentu ListView, u které jsem si zvykl používat featuru VirtualMode. VirtualMode umožňuje dynamicky generovat položky ListViewu z kolekcí objektů uložených jinde v paměti, takže když máte milion položek k zobrazení, nemusíte mít v paměti i milion položek ListViewu, ale vždy budete mít pouze ty, které jsou zrovna v oblasti, kterou vidí uživatel.

Přejete si vybrat nebo vybrat?


WakeOnLAN nástroj
WakeOnLAN nástroj

Mimo VirtualMode mám zapnutý i MultiSelect, takže uživatel může vzbudit více počítačů zároveň. Vypínání a zapínání položek tool menu a kontextového menu je řízeno počtem vybraných položek v ListView. A tady právě začíná ta správná sranda. Když je vybrána právě jedna položka, ListView.SelectedIndices.Count je 1. To je samozřejmě v pořádku. Když myší vyberete dvě položky, je SelectedIndices.Count 2. I to bych bral. Když ovšem vyberete dvě položky pomocí kláves Shift + šipka dolů, je SelectedIndices.Count 0. WTF? ListView totiž odpaluje dva typy eventů. Při výběru myší je odpálen event SelectedIndexChanged a SelectedIndices jsou nastaveny správně. Ovšem při výběru klávesnicí je odpálen event VirtualItemsSelectionRangeChanged, SelectedIndices jsou vynulovány a naplněny až ListView ztratí focus. Možná to nějaké ospravedlnění má, ale mě se to ani trochu nehodí.

Workaround


Přesto, že při volání VirtualItemsSelectionRangeChanged nejsou nastaveny SelectedIndices, dá se tento event použít alespoň k získání počtu vybraných položek, což je přesně to co mi stačilo. Event má totiž jako parametr třídu se sympaticky krátkým názvem ListViewVirtualItemsSelectionRangeChangedEventArgs, která obsahuje pole StartIndex a EndIndex. Rozdíl hodnot těchto dvou polí a připočtení dvojky dá dohromady počet vybraných položek. (Proč dvojka? Způsob indexování je poněkud obskurní, protože při výběru dvou položek je StartIndex i EndIndex 0).

V kombinaci s eventem SelectedIndexChanged se tedy pro počítání označených řádků dá vyrobit relativně rychlý workaround.

public partial class Form1 : Form
    {
        private int selectedItemsCount = 0;

        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            selectedItemsCount = listView.SelectedIndices.Count;
        }

        private void listView_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
        {
            selectedItemsCount = e.EndIndex - e.StartIndex + 2;
        }
    }

A voila, proměnná selectedItemsCount nyní obsahuje správný počet položek, ať je uživatel vybere jakkoliv. Co se týče ListView.SelectedIndices, ty, jak jsem již zmínil výše, budou nastaveny ve chvíli, kdy ListView ztratí focus, takže jejich další použití je možné.

Jen podotknu, že Microsoft o tomto problému ví od roku 2005. Zda jde o bug nebo works-as-designed se zatím nikdo nevyjádřil a pochybuju, že vůbec kdy vyjádří.