WinForms – tipy pro binding mezi prvky

WinForms jsou stále hojně používané při vytváření desktopových aplikací, i když mě by se celkem líbilo, pokud by byly postupně vytlačeny WPF jakožto tlustým klientem nebo v některých případech spíše Silverlightem v jeho variatně OutOfBrowser.

A právě z WPF a Silverlightu jsem si hodně zvyknul na propracovaný Binding a to jak na object binding, tak také Element binding. Což mě přivádí na myšlenku, proč se obdobného bindingu nevyužívá také ve WinForms aplikacích. Ještě větší radost jsem tak měl z dotazu, který adresoval jeden z tazatelů na builder.cz fóru o .NETu do diskuze, kdy se snažil vyřešit a refaktorovat svůj duplicitní kód.

Čekal jsem, že někdo uvede nějaké elegantní řešení, ale ani po několika dnech se tak nestalo a tak jsem si říkal, že by nebylo špatné nastínit způsob, jakým bych toto řešil sám.

Binding na prvky

Už od dob .net frameworku 1.0 existuje ve WinForms aplikacích možnost bindování prvků na zdroj dat, tímto zdrojem je pak nejčastěji BindingSource (ten tedy až od verze 2.0), ale samozřejmě můžeme bindovat na jakýkoliv jiný zdroj, který poskytuje data.

K tomuto bindingu slouží třída Binding, která nám umožňuje nastavit cílovou vlastnost uživatelského prvku, samotný zdroj a jeho vlastnost a dalších 5 vlastností, které se týkají formátování a okamžiku získávání/vykreslování dat do uživatelského prvku.

A právě tohoto bindingu je možné využít i pro interakci mezi prvky.

Jen abych přiblížil daný dotaz, jednalo se o to, že se několik uživatelských prvků (TextBoxů) mělo zpřístupnit nebo znepřístupnit v závislosti na zaškrtnutí nebo odškrtnutí CheckBoxu. Samozřejmě toto mělo být správně nastaveno již při prvním zobrazení formuláře. Většina odpovědí tak tedy byla volat vytvořenou metodu jak z obsluhy události Load daného formuláře, tak také z obsluhy události CheckedChanged pro daný checkbox. Právě tento stav, kdy bylo třeba volat danou metodu ze dvou různých míst se však tazatel snažil eliminovat.

Dlužno dodat, že to byl celkem legitimní požadavek a takovýchto nastavovacích metod může být několik, obzvláště v případech, kdy je třeba skrývat nebo různě nastavovat jednotlivé prvky uživatelské rozhraní v závislosti na dalších hodnotách.

Řešení první

Prvním, maličko jiným řešením může být to následující, které využívá skutečně jen bindingu na prvek uživatelského rozhraní. Tento přístup asi oceníte v okamžiku, kdy se bude jednat o jednoduchý formulář, kde skutečně chcete skrýt nebo jen zneaktivnit některé prvky v závislosti na prvku jiném.

textbox1.DataBindings.Add("Enabled", checkbox1, "Checked");
textbox2.DataBindings.Add("Enabled", checkbox1, "Checked");

tento kód můžeme umístit do obsluhy události formuláře Load a to je vše, co musíme udělat. Jakmile programově nebo uživatelskou interakcí změníme zaškrtnutí checkboxu, dojde zároveň k zneaktivnění nebo naopka zpřístupnění daných textboxů. Tím nám tak odpadne nutnost volat jakoukoliv metodu ze dvou míst a máme vše na jednom místě. (Samozřejmě můžeme tento kód vyvést ven z obsluhy Load a v této pouze volat nastavovací metodu pro binding.)

Řešení druhé

Druhým možným řešením je pak mít vytvořený model chování pro daný formulář na který jsou jednotlivé prvky bindovány. To s sebou nese i pozitiva ve formě možnosti takovýto model otestovat. Toto řešení bude určitě vhodné v případech, kdy budeme mít složitější formulář a budou existovat různé závislosti mezi prvky. Samozřejmě zde bude hodně záležet na přístupu, který jsme zvolili pro celý projekt a především s ohledem k tomu, zda používáme některý z prezentačních návrhových vzorů jako třeba - Model-View-Controller (MVC) nebo Model-View-Presenter (MVP).

Vezměme si příklad, kdy v závislosti na takto zaškrtnutém checkboxu chceme dva textboxy povolit, ale třeti textbox má být v takovou chvíli zakázan a naopak. Buď bychom mohli tuto situaci zapsat do logiky daného formuláře a nebo si vytvoříme samostatnou třídu, která nám tento model chování bude simulovat.

Jak by takový kód potom mohl vypadat je demonstrováno na následujícím příkladu:

public class Form1ModelBehavior
{
        public bool Checked { get; set; }

        public bool GroupAvailable
        {
                get
                {
                        return Checked;
                }
        }

        public bool SingleAvailable
        {
                get
                {
                        return !Checked;
                }
        }
}

Jak vidíte, jedná se o jednoduchou třídu, která vrací dva různé stavy na základě hodnoty nastavené do vlastnosti Checked. Samozřejmě tato třída by mohla být složitější v závislosti na daném UI.

Ještě nám zbývá nastavit binding pro jednotlivé prvky a navázat tento binding právě na tento náš model chování. V tuto chvíli však nebudeme využívat jen textboxů, ale musíme nabindovat také checkbox, který vše bude řídit a na jehož interakci s uživatelem musíme reagovat. Aby vše fungovalo správně, právě binding na checkbox bude tím nejdůležitějším a musíme zajistit to, že v okamžiku, kdy dojde ke změně jeho vlastnosti Checked, aby se nám aktualizoval i daný model a provedly se příslušné změny taktéž na textboxech. Zde vidíte příklad takového bindingu:

private Form1ModelBehavior _model;

private void Form1_Load(object sender, EventArgs e)
{
        _model = new Form1ModelBehavior();
        checkBox1.DataBindings.Add("Checked", _model, "Checked", false, DataSourceUpdateMode.OnPropertyChanged);
        textBox1.DataBindings.Add("Enabled", _model, "GroupAvailable");
        textBox2.DataBindings.Add("Enabled", _model, "GroupAvailable");
        textBox3.DataBindings.Add("Enabled", _model, "SingleAvailable");
}

Závěrem

Doufám, že se mi povedlo demonstrovat na jednoduchém příkladu snadnost a efektivnost využití bindingu ve WinForm aplikacích. Je třeba si v těchto chvílich uvědomit, že formulář není tím hlavním místem, kde bychom měli dělat veškerou práci a slouží skutečně jenom k uživatelskému vstupu a výstupu. A právě v takových chvílích nám může být hodně nápomocný binding.

V mnoha případech se však setkávám s tím, že do formuláře je přesunuta veškerá logika aplikace, čímž se takový projekt stává hůře spravovatelným a rozšiřitelným. Pokud si myslíte, že víte o nějakém dalším obdobném problému, určitě se o něj podělte, pokud k němu bude existovat stejně elegantní řešení, rád se pokusím o nalezení efektivnějšího řešení.

4 Comments

  • Petr said

    Ahoj, jde uvedené nastavení bindingu realizovat i pomocí designeru ve Visual Studiu? Jestli ano můžeš doplnit ukázku jak? Díky

  • Radek said

    Díky za článek. Měl bych ovšem dotaz k tomuto tématu:

    Zkouším dělat klasický formulář do kterého se předá nějaký byznys objekt (BO), formulář zobrazí hodnoty. Pokud se klepne na storno, nic se neuloží, pokud na OK, tak se hodnoty v BO změní. Problém je, že nejsem schopen nijak zkombinovat tuto možnost. Pokud u komponent nastavím datasourceupdatemode na OnPropertyChanged, hodnoty se změní okamžitě. Pokud nastavím na Never, hodnoty se nezmění nikdy a pokud nastavím na OnValidation, tak mám problém s tím, že "někdy" to funguje jak má a někdy ne, nejsem schopen na to přijít. V události OnClick na tlačítku OK mám pouze funkce this.Validation a this.ValidationChildern(), kde this je formulář.

    Asi je nutné, aby OnValidation mělo nějaké doplnění, bohužel nejsem schopen přijít na to, které.

    Nemáte nějaký tip?

    Děkuji!

Add a Comment