Jarda Jirava

Vývojář a architekt řešení postavených na technologii .net framework. Zabývám se jak vývojem webových aplikací za pomoci asp.net, tak také desktopových aplikací winform. Při návrhu řešení a samotném vývoji pak využívám dlouholetých zkušeností se zpracováním obchodní logiky a pravidel aplikací získaných z vývoje komerčních aplikací pro finanční a bankovní instituce.

Microsoft MVP

Microsoft MVP - Client App dev

Silverlight minesweeper

Get Microsoft Silverlight

Navrhi COOL design a vyhraj zkušební let po Evropě!

Poslední příspěvky

builder.cz

4
I

Jak na DesignMode a designer

Ač to může být pro zkušené vývojáře obehraná písnička, ještě stále se najde spousta nováčků, nebo i pokročilejších vývojářů, kteří mají problém s určením, kdy se jejich komponenta, nebo formulář nachází ve vývojovém prostředí designeru.

Malé odbočení na začátek

Dřív než začnu popisovat způsob, jak identifikovat, že se formulář nebo obecnějí komponenta nachází ve vývojovém prostředí v režimu designeru, dovolím si malou odbočku, jak a proč tento příspěvek vlastně vznikl.

Takovouto otázkou se zabýval jeden z přispěvatelů na .net fóru builder.cz - jak jistě víte, mém oblíbeném. V mé odpovědi jsem jej nasměroval na možné řešení. To se však nezdálo býti dostačující a tak se na mě obrátil i soukromě, zda bych mu neposkytl potřebný kód. Nebyl prvním, kdo takto učinil a nejednalo se ani o ojedinělý případ.

Co jsem si však v tu chvíli uvědomil, že za tyto poměrně kvalitní konzultace, bych mohl být odměněn. ;-) Když jsem mu toto navrhl, již nereagoval. Je to myslím škoda, a určitě se nebudu podobným konzultacím bránit, pokud budete mít konkrétní problém.

Jelikož k rozumné dohodě nedošlo a přispěvatel nejspíše problém vyřešil vlastními prostředky, zkusím se podělit o řešení aspoň touto cestou. Můžete to vzít, třeba jako referenci.

Zpátky k DesignMode

Jak jsem již v odpovědi na otázku uvedl, existuje několik možností zjištění, že je komponenta otevřena v designeru Visual Studia.

Testování DesignMode

První možností je testovat vlastnost DesignMode dané komponenty. To je funkční řešení do doby, než umístíme komponentu do nadřazeného kontejneru a pokusíme se tuto vlastnost otestovat. Vrátí nám hodnotu false. Což je nahlášeno jako bug. Samozřejmě jemožné projít všechny nadřezené komponenty a nakonec se správné hodnoty dobrat.

Testování Site

Další možností je také otestovat vlastnost Site, která vrací rozhraní ISite jenž je naplněna právě designerem (zjednodušeně řečeno).

Testování přítomnosti EntryAssembly

Třetí možností v řadě a nikoliv poslední je otestování toho, zda existuje vstupní Assembly. Vychází se z předpokladu, že designer spouští kód pro formulář nebo komponentu "po svém" a tak nedochází k nahrání vstupní assembly.

Zjištění přítomnosti služby IDesignerHost

Určitě ne poslední možností je zjištění přítomnosti konkrétní implementace rozhraní IDesignerHost, jenž zajišťuje služby designeru.

Pro lepší představivost uvedu kód, který určitě vydá za tisíce slov. Kód uvnitř podmínky se provede pouze v případě, že jsme v designeru:

if (Site != null) {
    System.Diagnostics.Debug.WriteLine("site");
}
if (Assembly.GetEntryAssembly() == null) {
    System.Diagnostics.Debug.WriteLine("entry assembly");
}
if (GetService(typeof(IDesignerHost)) != null) {
    System.Diagnostics.Debug.WriteLine("designer host service");
}
Snad to některým pomůže.
Publikováno pod: .net technology , builder.cz
1
I

Přidání obsahu do buňky v DataGridView

Uběhlo již mnoho času od chvíle, kdy jsem se naposledy věnoval některému z příspěvků na fóru o .net na serveru builder.cz. Nyní se však na tomto fóru objevilo několik začátečníků, kteří se nespokojí jen s ukázáním cesty, ale rádi by viděli i kus kódu, který jim vydláždí cestu k vytouženému cíly.

Jeden z požadavků uveřejníl i přispěvatel lopy123, který by rád věděl, jak je možné přidat obsah do buňky v DataGridView. Celou otázku a diskuzi je možné sledovat ve zmíněném vlákně. Já jsem otázku dlouhou dobu registroval, ale blíže jsem se jí nevěnoval a to hned z několika důvodů.

Jedním z oněch důvodů byl již požadavek samotný, kdy mi přišlo chování poněkud netypické a nestandardní. V případě, kdy požaduji vložit, resp. přidat text k již existujícímu textu, nejdříve vstoupím do buňky a až následně si vyberu místo, kam text vložím.

Přejdu však k řešení, ke kterému jsem se nakonec odhodlal. Možná proto, že odpovědi, které jsem si postupně četl se přesunuly až k použití win API funkcí a to bylo v takovém případě celkem zbytečné.

Jednoduchý kód řešící problém

Výsledný kód řešící daný problém je vcelku jednoduchý. Je třeba se jen maličko zamyslet nad tím, co se vlastně v datagridview "děje", když uživatel chce editovat text uvnitř buňky.

Nejdříve si tedy registrujeme odběr události, ke které dochází při zobrazení editovacího prvku, konkrétně tedy EditingControlShowing. To můžeme udělat v okně Properties daného DataGridView, kdy se nám vygeneruje do obslužného kódu aplikace šablona metody. Do této šablony pak doplníme obsluhu události.

private void gridLopy_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
    TextBox txt = e.Control as TextBox;
    if (txt != null) {
        txt.KeyPress -= txt_KeyPress;
        txt.KeyPress += txt_KeyPress;
    }
}

Jediné, co v obsluze provádím je, že zjišťuji, zda editovacím prvkem je TextBox - pro ostatní prvky to nemá příliš význam (samozřejmě, pokud nebudeme implementovat nějaký vlastní textový editor). Poté, nejen pro jistotu, odregistrujeme událost stisku klávesy KeyPress a následně si zaregistrujeme její odběr.

Proč dochází nejdříve k odregistrování odběru události? Odpověď je opět velice jednoduchá. DataGridView používá vždy pouze jednu instanci editovacího prvku pro daný typ. Tudíž, pokud by nedošlo nejdříve k odregistraci události, tyto by se postupně vršili a docházelo by k jejich několikanásobnému vyvolávání, což je v tomto případě nežádoucí.

Poté je již dostačující vhodně ošetřit přidání znaku na potřebné místo. Samozřejmě by bylo možné implementovat lepší algoritmus, ale pro "nástřel" možného řešení jsem zvolil přidání znaků nebo číslic na konec textu.

void txt_KeyPress(object sender, KeyPressEventArgs e) {
    TextBox txt = sender as TextBox;
    if (txt != null) {
        if (char.IsLetterOrDigit(e.KeyChar)) {
            txt.Text = txt.Text + e.KeyChar;
            txt.SelectionStart = txt.Text.Length ;
            e.Handled = true;
        }
    }
}

Jak je vidět, výsledný kód není skutečně složitý. Co je však důležité, pochopit princip práce DataGridView. Snad to přispěvateli lopy123 a nejen jemu pomůže.

Publikováno pod: .net technology , builder.cz
6
VIII

ASP.NET vázání dat na zobrazovací prvky

Kdy správně získat data z datového zdroje a data nabindovat na zobrazovací ovládací prvky, to bylo tématem dnešního rozhovoru a já slíbil, že zkusím obhájit své stanovisko.

Zapředl jsem se totiž do rozhovoru s Martinem a to na základě otázky a jeho odpovědí z tohoto příspěvku. Tazatel se ptal, zda existuje funkce na obnovení dat v GridView a přispěvatel se jménem studentik mu odpověděl, nechť použije událost PreRender. Martin poté upozornil, že tato událost není tím správným místem, kde provést dotaz do datového zdroje a nabindovat data. Odkázal přitom na stránku věnovanou tomuto tématu ASP.NET Page life cycle overview, kde jsou popsány jednotlivé události stránky Page.

Já vycházel z mého již dříve napsaného článku o životním cyklu stránky a zkušeností, které mám s tvorbou asp.net aplikací a nemohl jsem s Martinem souhlasit.

Kdy tedy provést navázání dat?

Nemohu souhlasit s tvrzením, že vázat data na zobrazovací prvky by se mělo již v události Init událostního modelu stránky. V tomto kroku jsou inicializovány jednotlivé prvky a rekonstruuje se objektový model stránky. Samozřejmě, pokud nemáme k dispozici ViewState je to pravé místo, pro to jej "nahradit" a získat tak obraz zobrazovaných dat, tak aby se mohli vytvořit všechny potřebné zobrazovací prvky i s jejich, na klienta, odesílanými údaji.

Posléze, v následujícím zpracování životního cyklu stránky, dojde k načtení dat z ViewState - v případě jeho vypnutí simulujeme napojením dat - a následně k nastavení hodnot formulářových prvků hodnotami z předané kolekce Forms.

Život stránky jde dál a následuje událost Load stránky, kdy se většinou provádí test na vlastnost IsPostBack a případné nahrání a navázání dat. Začátečnickou chybou tak je, že nedojde k otestování zda došlo k postbacku a tak si vývojář přepíše získaná data jejich původní hodnotou a nadělá si spoustu problémů, které neví jak řešit. I proto se mi jeví navázání dat v tomto okamžiku jako nevhodné a vlastně se tím nic nezíská.

A to i vzhledem k tomu, že následuje vyvolání a zpracování změnových např. textchanged a postback událostí - pro prvky implementujících rozhraní IPostBackEventHandler. Kde se snažíme o uložení dat zaslaných klientem do naší aplikace a zároveň můžeme na základě jeho rozhodnutí provádět další operace s naší aplikací. Tyto operace pak mohou vést k tomu, že zdroj dat bude jiný nebo bude obsahovat jiné hodnoty, které v okamžiku zpracování předchozích události ještě nevíme - nebyli jsme o nich informováni, samozřejmě si je můžeme zjistit již dříve, například přímým dotazem na hodnotu do kolekce Forms.

Získání dat a jejich navázání

A právě po proběhnutí výše uvedených akcí je myslím nejvhodnější doba, kdy získat nová data a provést jejich navázání na zobrazovací prvky. Pro verze asp.net 1.x je tedy možné využít události PreRender,pro asp.net 2.0, kde byl událostní model rozšířen, je to událost LoadComplete - samozřejmě je též možné využít PreRender událost.

Určitě se ptáte, jaké k tomu mám podklady, že doporučuji zrovna tyto události. Na prvním místě jsou to osobní zkušenosti, které jen těžko něco může nahradit. Již zmíněné chování, kdy dochází k postupnému "uvědomování si" co vlastně uživatel chce zobrazit až třeba po implementaci asynchronního volání ve verzi asp.net 2.0. Kde je možné si zaregistrovat počáteční a koncový handler v metodě AddOnPreRenderCompleteAsync a kdy ke zpracování asynchronního volání dojde v okamžiku mezi voláním metod OnPreRender a OnPreRenderComplete - tedy taktéž je využito tohoto okamžiku, abychom výsledek zdlouhavé operace navázali na zobrazovací prvek ve stránce.

Samozřejmě budu rád, pokud se mnou nebudete souhlasit a opravíte mě, já se alespoň poučím. Uvítám však i opačný názor, že nejsem sám, kdo k tomuto řešení dospěl.

Publikováno pod: .net technology , builder.cz
12
VI

Lepší je se ptát

než odpovídat, potom to alespoň nevypadá jako, že jste blbec, když neznáte odpověď. Mimochodem, to mi lehce připomělo báječnou hru Blbec k večeři v nastudování divadla Bez zábradlí.

Ale zpátky k tomu, o čem jsem se chtěl rozepsat. Jak jistě víte, již dlouhou dobu jsem členem komunity kolem serveru builder.cz a jeho fóra o .net. Nedávno jsem dokonce uspořádal i první neformální setkání přispěvatelů tohoto fórá, ale to už opět odcházím od tématu.

Proč je tedy lepší se ptát? Nejste totiž limitováni ničím, nejspíše řešíte problém, který zrovna pálí vás a nikoho jiného a v hlavě máte nějakou ideu. Tuto ideu se potom snažíte s větším či menším úspěchem přetransformovat v otázku a pak už jenom čekáte a říkáte si: Milé fórum, poper se s tím jak umíš. Samozřejmě je tu i druhá strana, kdy se ptáte, protože se snažíte někoho zkoušet a odpověď už dávno znáte, ale to není zrovna náš případ.

Ač nemám zrovinka rád různá přirovnání, k některým otázkám z poslední doby bych jedno takové, lehce kulhající, měl. Otázka vypadá asi následovně.

Mám dvě kola, řídítka, nějaký morot a kousek drátu se špagátem. Milé fórum, slyšel jsem, že z toho udělám auto. Bude mi stačit francouzský klíč nebo mám použít hever?

Ne ne, tohle není problém jenom u otázek, týkajících se .netu, ono se stačí podívat i do ostatních diskuzí a přijdete na ještě větší perly. O těch zase někdy příště.

Jak se také zeptat?

Některé určitě napadne otázka, jak je tedy lepší se zeptat, co je na takové otázce špatně a jak ji zlepšit. Vždyť co, vyjmenoval jsem snad vše, co bylo možné a co vím a znám.

Co mi však na takové otázce schází, možná jenom mě, je cíl, dílčí nebo konečný výsledek, našeho snažení. Tazatel vesměs všechno má, jenom neví jak to dát dohromady. Nezabývá se již otázkou, zda mu něco neschází, zda je možné se takto k výsledku dobrat, řeší už jenom konkrétní způsob a pokud možno konkrétní implementaci.

Odpovídající na straně druhé chvíli přemýšlí a snaží se podané informace napasovat někam do svých současných znalostí, takříkajíc čte mezi řádky ta nevyřčená slova a hledá za nimi hlubší smysl a význam takového snažení a nutnosti položit takto koncipovanou otázku, jenže ve většíně případů je o pověstný chlup vedle. Potom si připadá skutečně jak ten blbec.

4
II

ADO.NET a AcceptChanges

Fórum o .net na serveru Builder je v současné chvíli poměrně širokou přehlídkou začátečnických chyb a odpovědí  na ně. Nejednomu začínajícímu programátorovi v .net tak může pomoci vstoupit do světa vyspělých technologií. Stačí jen jediné, prohlédnout si ani ne tak vzdálenou historii a většinou by dotazující se získal odpověď, na svoji ještě nepoloženou otázku, téměř okamžitě. Jelikož jsem autorem poměrně značné části odpovědí na nejen tyto otázky, rozhodl jsem se některé z nich vybrat a uveřejnit je zde, jako takové malé FAQ nebo spíše knowledge-base, aby byly přeci jen lépe dostupné. Byť je možné stále si stáhnout historii konference pro offlline prohlížení.

DataSet.AcceptChanges a neukládání dat

Poměrně častým dotazem je, proč se nechtějí ukládat data do datového zdroje, přestože jsou vidět v datasetu a nad tímto je prováděn Update pomocí DataAdapteru, případně ve verzi .net 2.0 pomocí TableAdapteru.

Ona záludnost tkví v tom, že začátečník, snažící se o zapsání všech dat volá všechny metody, na používaných objektech. Tudíž velice správně zavolá metodu EndEdit() pro zapsání editovaných změn do datového objektu většinou DataSetu.

Co je však již špatně, zavolání metody AcceptChanges() nad tímto DataSetem ještě před provedením zapsání změn do datového zdroje. Vykonáním této metody totiž dojde k akceptování všech změn (jak je také patrné z názvu metody) a nastavení stavu všech řádků v jednotlivých tabulkách na hodnotu DataRowState.Unchanged. Následné volání metody Update() nad TableAdapter/DataAdapter potom nemá jak poznat, které řádky má aktualizovat, vložit nebo smazat a neprovede tak žádnou akci.

AcceptChanges tedy není nutné volat

a to v případech, kdy zapisujeme data do datového zdroje, neboť tuto metodu zavolá DataAdapter/TableAdapter sám, po provedení zápisu změn do databáze.

Publikováno pod: .net technology , builder.cz , knowledge base
10
XI

Splash screen a ApplicationContext

Vidět reakci ihned po spuštění programu, je přesně to, po čem touží snad každý uživatel aplikace. Vznikají tak různé splash screeny, které informují uživatele co se právě s aplikací děje. Pro tvůrce aplikace je to něco navíc, co dává ke své aplikaci a přitom by se žádná aplikace neměla bez této úvodní obrazovky objevit. Ano, ano, existují výjimky, ale jestliže aplikace je komplexnější a při startu je potřeba provést náročnější inicializaci, je splash screen nejvhodnějším řešením.

Způsobů jak vytvořit tento úvodní formulář určitě existuje několik, já se chci věnovat jednomu z nich, který považuji za velice elegantní a přímočarý. Určitě však nečekejte, že zde objevíte nádherný formulář, mě zajímalo spíše to, co se skrývá tak říkajíc pod kapotou a jak aplikaci vhodně inicializovat.

Použití ApplicationContext třídy

Jednou z možností jak zahájit zpracování smyčky zpráv je vložení instance třídy ApplicationContext jako parametru do metody Application.Run(). Takto vytvořená instance třídy musí obsahovat odkaz na formulář, který bude sloužit jako context. Při zavření tohoto formuláře poté dojde k ukončení aplikace. To ovšem nemusí platit v okamžiku, kdy je přepsána metoda OnMainFormClosed() a vlastnost MainForm je nahrazena za jinou instanci formuláře, který v ten okamžik převezme context aplikace.

Právě tohoto způsobu můžeme využít k vytvoření splash screenu, inicializaci aplikace a zobrazení hlavního okna aplikace.

Inspirace

Teď určitě někteří mohou namítnout, že takových příkladů je možné najít na internetu dostatek. Což je pravda, ale přesto uvedu své řešení, i z toho důvodu, že jsem na stejné nenarazil a většinou všem takovým příkladům scházelo to hlavní, úvodní inicializace aplikace. Můžete tak najít řešení, která používají timer, vykreslují krásné splash screeny a podobně.

Ač by určitě nebyl problém takovou inicializaci doplnit, jsou zde začátečníci, kterým takovýto návod přijde jistě vhod.

Co budeme potřebovat

Určitě to bude nějaký pěkně vyvedený formulář, který bude sloužit jako splash screen. Jakým způsobem bude takový formulář vypadat nechám na vkusu každého, co však bude zajímavější je jeho jedna případně dvě public metody, které budou aktualizovat stav inicializace a informovat uživatele. Dále to bude formulář hlavní, se kterým bude uživatel aplikace pracovat, ten je v plné režii vývojáře. A v neposlední řadě je to třída, ktérou pojmenujeme třeba SplashApplicationContext a jenž je poděděná od třídy ApplicationContext.

SplashApplicationContext

Tato třída provádí vše co budeme potřebovat k zobrazení splasch screenu a následnému zobrazení hlavního okna aplikace. Zároveň se postará o to, že dojde k potřebné inicializaci aplikace (dynamicky nahrávané moduly, připojení ke vzdálenému serveru a natažení úvodních dat, atd.) a to v samostatném vlákně. Po dokončení inicializace, které může trvat proměnlivě dlouhou dobu, pak dojde k zobrazení hlavního formuláře.

Co je důležité si uvědomit je, že inicializace běží v samostatném vlákně aplikace a tak veškerá volání nad prvky formuláře nebo i formulářem samotným musí být kontrolována a vyvolána z vlákna hlavního (UI).

Třída SplashApplicationContext tak může vypadat následovně:

public class SplashApplicationContext : ApplicationContext
{
  private FormSplash _splash;
  private FormMain _main;
  public SplashApplicationContext () {
    _splash = new FormSplash();
    this.MainForm = _splash;
    // Thread.CurrentThread.Name = "mainThreadUI"; abychom snáze identifikovali v jakém vlákně se nacházíme
    Thread thread = new Thread(new ThreadStart(initialize));
    // thread.Name = "initializeThread";
    thread.Start(); // spustíme inicializaci
  }
}

Po provedení konstruktoru se nastartuje inicializace aplikace v samostatném vlákně, ihned poté dochází k vložení instance contextu do smyčky aplikace - Application.Run(new SplashApplicationContext()) - a zobrazení úvodního formuláře.

V metodě initialize() dochází jednak k inicializaci aplikace a také k aktualizaci stavu, který informuje uživatele o prováděných krocích.

private void initialize() {
  _splash.PerformStep("Nahrání uživatelského profilu");
  // zde je nahrání uživatelského profilu
  _splash.PerformStep("Nahrání doplňkových modulů");
  // nahrajeme nutné doplňkové moduly
  splashClose();
}

Po provedení inicializace, na samém jejím konci dochází k volání zavření formuláře. Proč není volána přímo metoda Close() je bystřejším již jasné, nejsme totiž v hlavním vlákně aplikace a tak bychom se mohli dočkat výjimky. Metoda splashClose(), stejně tak jako metoda PerformStep() tak využívají vlastnosti InvokeRequired a případně vyvolají samu sebe.

private void splashClose() {
  if (_splash.InvokeRequired) {
    _splash.Invoke(new MethodInvoker(splashClose));
  } else {
    _splash.Close();
  }
}

Ještě dříve než skončíme a budeme moci naši skvělou aplikaci spustit a kochat se úvodním formulářem, musíme přepsat metodu OnMainFormClosed().

protected override void OnMainFormClosed(object sender, EventArgs e) {
  if (sender is FormSplash) {
    _main = new FormMain();
    this.MainForm = _main;
    this.MainForm.Show();
  } else {
    base.OnMainFormClosed (sender, e);
  }
}

V metodě nejdříve otestujeme, zda dochází k zavření formuláře představujícího náš splash screen, pokud je tomu tak, instancujeme hlavní formulář a přiřadíme ho proměnné MainForm a formulář zobrazíme, tím získá/převezme tento hlavní formulář kontext aplikace. V opačném případě dojde k ukončení aplikace.

Máme hotovo

Tím máme úkol splněn a můžeme aplikaci s dobrým pocitem předat uživatelům, ti pak budou aplikaci vnímat o něco lépe.

Publikováno pod: .net technology , builder.cz , code snippet