IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
Štefan Kiss
Člen
Avatar
Štefan Kiss:22.10.2019 1:05

Dobrý deň, priatelia, som nováčik v C# a WPF. Cez 10 rokov som pracoval v Delphi a teraz prechádzam na Visual Studio. Chcel by som sa na Vás obrátiť s možno banálnym problémom, s ktorým ale neviem pohnúť. Mám aplikáciu s jedným listboxom a triedou, ktorá implementuje INotifyProper­tyChanged. Táto trieda je nastavená ako kontext pre okno s listboxom a obsahuje tiež list objektov, na ktorý binduje samotný listbox. Ak sa obsah listu zmení, vyvolám udalosť zmeny toho listu a vďaka bindingu sa obsah listboxu na obrazovke zmení. Všetko teda funguje ako má. Problém je v tom, že ak dôjde k zmene obsahu listboxu, potrebujem zariadiť aj to, aby bola niektorá položka vybratá. Dalo by sa to riešiť tiež cez binding, ak by som vlastnosť ListBox.Selec­tedItem nabindoval na nejakú property v kontexte. To ale spôsobí iba vysvietenie položky na obrazovke a ja ju potrebujem aj fokusnúť. Robím to takýmto kódom:

zoznamListBox­.SelectedIndex = index;
zoznamListBox­.SelectedItem = index;
zoznamListBox­.ScrollIntoVi­ew(zoznamListBox­.SelectedItem);
var listBoxItem = (ListBoxItem)zoz­namListBox.Item­ContainerGene­rator.Container­FromItem(zoznam­ListBox.Selec­tedItem);
listBoxItem.Fo­cus();

Otázka znie: kam tento kód umiestniť, aby sa vykonal po tom, ako je dokončený update listboxu a všetky položky sú už načítané? Je po tejto akcii vyvolaná nejaká udalosť? Skúšal som udalosť Loaded, ale to mi zafunguje len po štarte aplikácie pri prvom načítaní obsahu a nie pri reloade, ktorý nastane po vyvolaní zmeny na zdroji. Ako by ste to, prosím, riešili? Ďakujem.

 
Odpovědět
22.10.2019 1:05
Avatar
Hans
Člen
Avatar
Odpovídá na Štefan Kiss
Hans:22.10.2019 10:21

Taková funkcionalita patří do samostatné třídy, které se říká attached behavior, kterou pak attachneš na daný control.
Jako příklad tady mám behavior, který při změně itemssource u comboboxu vybere vždy první item, od toho bys měl být schopný se odpíchnout.

public class ComboBoxSelectBehavior
    {

        /// <summary>
        /// Dependency properta, který mi říká, že chci při změně zdroje
        /// </summary>
        public static DependencyProperty SelectFirstOnNewSourceProperty = DependencyProperty.RegisterAttached("SelectFirstOnNewSource", typeof(bool), typeof(ComboBoxSelectBehavior), new UIPropertyMetadata(false, SelectFirstOnNewSourceChanged));

        /// <summary>
        /// Getter
        /// </summary>
        public static bool GetSelectFirstOnNewSource(DependencyObject obj)
        {
            return (bool)obj.GetValue(SelectFirstOnNewSourceProperty);
        }
        /// <summary>
        /// Setter
        /// </summary>
        public static void SetSelectFirstOnNewSource(DependencyObject obj, bool value)
        {
            obj.SetValue(SelectFirstOnNewSourceProperty, value);
        }

        /// <summary>
        /// Změna ddependency property, registrace
        /// </summary>
        public static void SelectFirstOnNewSourceChanged(
           DependencyObject d,
           DependencyPropertyChangedEventArgs e)
        {
            var comboBox = d as ComboBox;
            if (comboBox == null) return;
            bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;

            if (newValue == oldValue) return;
            if (newValue)
            {
                var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(comboBox)["ItemsSource"];
                itemsSourcePropertyDescriptor.AddValueChanged(comboBox, ComboBox_ItemsSourceChanged);
            }
            else
            {
                var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(comboBox)["ItemsSource"];
                itemsSourcePropertyDescriptor.RemoveValueChanged(comboBox, ComboBox_ItemsSourceChanged);
            }
        }
        /// <summary>
        /// Eventa o změně zdroje
        /// </summary>
        private static void ComboBox_ItemsSourceChanged(object sender, EventArgs e)
        {
            var comboBox = (ComboBox)sender;
            if (comboBox.Items.Count > 0)
                comboBox.SelectedIndex = 0;
            else
                comboBox.SelectedIndex = -1;
        }

    }

použití je pak v xamlu

<ComboBox behav:ComboBoxSelectBehavior.SelectFirstOnNewSource="True" />

výhodou je, že tato funkcionalita je izolovaná a znovupoužitelná a prostě SOLID :)

 
Nahoru Odpovědět
22.10.2019 10:21
Avatar
Štefan Kiss
Člen
Avatar
Štefan Kiss:22.10.2019 16:23

Ďakujem. Ak tomu správne rozumiem, tak vlastne rozširuješ triedu ComboBox o novú metódu SelectFirstOn­NewSource, ktorá by sa mala vykonať, keď sa zmení zdroj. Alebo mi niečo uniklo? Trochu ma mätie, že v xamli priraďuješ
<ComboBox behav:ComboBox­SelectBehavior­.SelectFirstOn­NewSource="Tru­e" />
hoci samotnú vlastnosť SelectFirstOn­NewSource v triede ComboBoxSelec­tBehavior vôbec nemáš. A ešte načo je tam vlastne tá dependencyproperta? Nestačilo by jednoducho ošetrovať udalosť ItemSourceChanged? Sorry za možno blbé otázky, ale chcem veciam porozumieť, nie iba mechanicky opisovať kódy. Vďaka .

 
Nahoru Odpovědět
22.10.2019 16:23
Avatar
Hans
Člen
Avatar
Hans:23.10.2019 7:46

Ne, není to rozšíření třídy ComboBox, ale je to vytvoření tzv. attached property
Ta se na daný control pouze "připojuje", ale control ji nezná. Je to to samé jako např.

<Grid>
        //definice řádků a sloupců...
        <Combobox Grid.Row="1" />
<Grid />

ComboBox taky neví nic o Grid.Row, ale je možné na něj tuto property připojit.

hoci samotnú vlastnosť SelectFirstOn­NewSource v triede ComboBoxSelec­tBehavior vôbec nemáš
mám, je to dependency property definovaná jako

public static DependencyProperty SelectFirstOnNewSourceProperty = DependencyProperty.RegisterAttached("SelectFirstOnNewSource", typeof(bool), typeof(ComboBoxSelectBehavior), new UIPropertyMetadata(false, SelectFirstOnNewSourceChanged));

Nestačilo by jednoducho ošetrovať udalosť ItemSourceChanged
Asi by to stačilo, ale tohle je obecnější a zapouzdřenější řešení.

Doufám, že jsem to podal trochu srozumitelně. Doporučuju si alespoň krátce nastudovat, co je Dependency property a Attached property, protože ve WPF je to celkem základní věc.

 
Nahoru Odpovědět
23.10.2019 7:46
Avatar
Štefan Kiss
Člen
Avatar
Štefan Kiss:5.11.2019 0:18

Ahoj, tak po čase som sa k problému znovu vrátil a keďže som stále nebol schopný dobrať sa k úspešnému koncu, urobil som si projekt s jedným comboboxom a jedným tlačidlom "obráť poradie". Vytvoril som si triedu Kontext a do nej dal list stringov, ktorý sa v konštruktore naplní zopár položkami. Combobox som v xamli nabindoval na tento list a potom som si skopíroval presne tú triedu, čo si sem dal. No a výsledok je taký, že po spustení programu sa vytvorí okno, vytvorí sa kontext a list s položkami. Vtedy sa generuje aj metóda ComboBox_Item­sSourceChanged a vyberie sa prvá položka. Ale ak kliknem na tlačidlo "Obráť poradie", čo urobí iba List.Reverse() tak položky v comboboxe sa obrátia ale udalosť sa už nevyvolá a nevyberie sa prvá položka. Takže vlastne ani presným skopírovaním Tvojho kódu som nedosiahol to, aby sa výber prvej položky urobil po akejkolvek zmene obsahu comboboxu resp zdrojového listu. Kde môže byť chyba resp. ako na to?

 
Nahoru Odpovědět
5.11.2019 0:18
Avatar
Odpovídá na Štefan Kiss
Michal Štěpánek:5.11.2019 12:07

Pokud neměníš zdroj dat, tak se logicky ta událost vyvolat nemůže. Můžeš si zkusit tu událost vyvolat ručně hned po List.Reverse(), nebo si můžeš ten kód napsat do extra funkce a vyvolávat to tam...

Nahoru Odpovědět
5.11.2019 12:07
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Štefan Kiss
Člen
Avatar
Štefan Kiss:5.11.2019 12:44

No pôvodne som sa o to pokúšal. Hneď po zavolaní reverse som ručne vyvolával aj onú udalosť aj priamo metódu, ktorá by vybrala a fokusla položku v listboxe. Lenže vtedy program často padal s hláškou, že sa pokúšam vyberať a fokusovať neexistujúci objekt. Evidentne teda ešte update listboxu neprebehol a práve preto som chcel aby sám listbox po načítaní nových dát vyvolal udalosť že už mám všetky dáta update prebehlo v poriadku som pripravený. Kedysi som v Delphi toto riešil cez udalosť onchanged ktorú tam mal listbox a ktorá sa vyvolala po akejkoľvek zmene obsahu komponenty, teda po pridaní položky, vymazaní, preskupení, načítaní nových dát. Toto mi tu presne chýba aby som jednoducho ošetril udalosť, ktorá je vyvolaná po zmene obsahu listboxu. Akosi sa mi nechce veriť že pri tak komplexnom systéme takú banálnu vec nejde odchytiť. Skúšal som aj udalosť Loaded, ale aj tá je generovaná len po prvom načítaní dát a potom už nie, čo je tiež podla mňa škoda, pretože dáta sa loadujú stále. Ak by teda mal niekto nejaký nápad, bude to vítané, inak pokračujem v pátraní a v úplne najhoršom sa vykašlem na binding a urobím si vlastnú metódu UpdateListbox, ktorá v cykle cez add nahádže do listboxu položky zo zdroja dát a nakoniec vyberie a fokusne požadovaný záznam. Trocha amatérske ale zaručene funkčné.

 
Nahoru Odpovědět
5.11.2019 12:44
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 7 zpráv z 7.