Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc

C# Projects

Malá řešení pro velké projekty.
LivingIndicator: .NET Compact Framework Control

Začínající programátoři kteří se pustili do křížku s potvůrkou zvanou .NET Compact Framework jistě ocení můj příspěvek do diskuze. Od první verze prošel Compact Framework dosti značným vývojem, nicméně i ve verzi 2.0 existuje mnoho věcí které mohou programátorům zvyklým na ten 'tlustý' framework chybět. Jednou z nich je jistě absence dostatečného množství ovládacích prvků.

Ve své praxi jsem se setkal s mnohými požadavky na funkčnost a ovládání programu pro Pocket PC a nyní i Windows Mobile platformu. Jedním z nich byl požadavek na vytvoření ovládacího prvku, který by indikoval běžící program aniž by zablokoval vstup z dotykové obrazovky. Compact Framework má samozřejmě možnosti jak toto udělat. Objekt Cursors.WaitingCursor je v mnoha případech použitelný, nikoli však v tomto, protože to je právě ten případ který zablokuje uživatelský vstup. Po pravdě řečeno, nebyl by problém pověsit na objekt Timer standardní ovládací prvek typu ProgressBar a nechat jej běhat dokola - pumpovat. Ovšem pro hračičky a ty z vás, kteří mají raději elegantnější způsoby tu mám toto řešení.

Řešení ukazuje, jak vytvořit ovládací prvek, který se podobá indikátoru ve Windows XP, nebo snad Windows 2000 při jejich bootování. Jedná se o dvoubarevný pruh, resp. pruh kde dvě barvy tvoří přechod a přecházejí jedna ve druhou a tento přechod rotuje. Nebudu se dále snažit popisovat vzhled tohoto ovládacího prvku, nebo se do toho zamotám. Modří jistě již vědí a ostatní se určitě dovtípí.

A jak toto vytvořit v podmínkách .NET Compact Frameworku 2.0?

Namespaces které budeme potřebovat jsou následující:

 

using System;

using System.Drawing;

using System.Windows.Forms;

 

V první řadě je třeba vytvořit vlastní třídu, která je poděděná ze třídy System.Windows.Forms.Control. Tuto třídu jsem nazval LivingIndicator. Do následujícího bloku zahrnuji její definici až po konstruktor, abych příspěvek příliš nekouskoval.

 

public partial class LivingIndicator : Control

{

    // privátní bitmapy do kterých budeme kreslit

    private Bitmap offScreenBitmap = null;

    private Bitmap gradientBitmap = null;

 

    // oblast ovládacího prvku

    private Rectangle clientRectangle;

 

    // Timer zajišťující animaci

    private Timer indicatorTimer = null;

 

    // aktuální pozice posunu bitmapy

    private int currentPosition = 0;

 

    // krok ve kterém se bitmapa bude posouvat

    private const int SHIFT_VALUE = 20;

 

    // konstruktor

    public LivingIndicator()

    {

        InitializeComponent();

    }

    ....

    ....   

    ....

}

       

Konstruktor tohoto ovládacího prvku bude obsahovat jedinou funkci a tou je funkce InitializeComponent() kterou vám vygeneroval designer při založení projektu typu Control.

Další funkcí kterou jsem napsal je funkce, která vytvoří GDI objekt. Co je myšleno GDI objektem? Je to oblast kam umístíte ovládací prvek. Pro představu: ovládací prvek se skládá z obdélníkové oblasti dané šířky a výšky a bitmapy která vyplňuje tuto oblast. Šírku a výšku můžete měnit jak za běhu programu, tak hlavně v režimu návrhu, kdy ovládací prvek umisťujete na formulář. Ve chvíli kdy tyto hodnoty změníte, musí se zmenit i velikost bitmapy kterou vykreslujete a tak funkce provádí svůj vnitřní kód jen v případě že se změnily proporce ovládacího prvku.

Poznámka: Nedejte se zmýlit tím, že se bitmapa mění i v průběhu její rotace. Fakticky mění jen svou pozici a počátek vykreslení, nikoli však rozměry a pořadí poskládaných barev.

 

private void CreateGDIObjects()

{

    // pokud se změnily proporce ovládacího prvku

    if (clientRectangle == null || clientRectangle.Width != this.ClientRectangle.Width - 1 || clientRectangle.Height != this.ClientRectangle.Height - 1)

    {

        // vytvoř novou klientskou oblast na základě rozměrů ovládacího prvku

        clientRectangle = new Rectangle(0, 0, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);

 

        // vytvoř novou bitmapu o rozměrech ovládacího prvku

        gradientBitmap = new Bitmap(this.Width, this.Height);

 

        // vytvoř objekt Graphics

        Graphics gGradient = Graphics.FromImage(gradientBitmap);

 

        // počet vertikálních linek (polovina proto, že přechod je tvořen vlastně dvěmi stejnými bitmapami otočenými proti sobě o 180 stupňů

        int numOfLines = (this.ClientRectangle.Width) / 2;

 

        // počáteční a koncová barva přechodu - vlastnosti si vypůjčím z bázové třídy (nač tvořit vlastní?)

        Color foreColor = ForeColor;

        Color backColor = BackColor;

 

        // interval mezi barvami pro jednotlivé složky

        float redInterval = (foreColor.R - backColor.R);

        float greenInterval = (foreColor.G - backColor.G);

        float blueInterval = (foreColor.B - backColor.B);

 

        // koeficient kterým budeme násobit hodnoty jednotlivých složek v každém kroku vykreslení

        float redCoef = redInterval / numOfLines;

        float greenCoef = greenInterval / numOfLines;

        float blueCoef = blueInterval / numOfLines;

 

        // vykreslení základní bitmapy

        for (int i = 0; i <= numOfLines; i++)

        {

            int cRed = (int)(BackColor.R + ((float)i * redCoef));

            int cGreen = (int)(BackColor.G + ((float)i * greenCoef));

            int cBlue = (int)(BackColor.B + ((float)i * blueCoef));

            Color currentColor = Color.FromArgb(cRed, cGreen, cBlue);

            gGradient.DrawLine(new Pen(currentColor), i, this.Height, i, 0);

            gGradient.DrawLine(new Pen(currentColor), this.Width - i, this.Height, this.Width - i, 0);

        }

    }

}

 

Tak to je funkce pro vykreslení ovládacího prvku v klidovém stavu po té, co mu byly nastaveny hodnoty výšky a šířky. Další funkce kterou je nutné napsat, je funkce OnPaintBackground() našeho objektu. Modří jistě vědí že tato metoda je metodou bázové třídy a je volána vždy při překreslení ovládacího prvku. Vzhledem k tomu, že náš ovládací prvek si malujeme sami, není další překreslování žádoucí, takže naše funkce bude vypadat následovně:

 

protected override void OnPaintBackground(PaintEventArgs e)

{

 

}

 

Ano, funkce nebude mít ve svém těle vůbec nic. Tím zajistíme, že tato metoda v bázové třídě bude provádět to co potřebujeme, tedy nic.

Poznámka: Pokud si budete chtít vyzkoušet, jak by to vypadalo bez této metody, tak můžete místo toho abyste ji mazali, do jejího těla vložit řádek base.OnPaintBackground(e);

Další metodou bázové třídy kterou musíme přepsat je metoda OnPaint. Ta zajišťuje vykreslení ovládacího prvku které budeme iniciovat metodou Refresh.

 

protected override void OnPaint(PaintEventArgs pe)

{

 

    // zavoláme si funkci na vytvoření základní bitmapy

    CreateGDIObjects();

 

    // vytvoření bitmapy do paměti - a zase jen v případě že neexistuje a nebo se změnily proporce´

    if (offScreenBitmap == null || offScreenBitmap.Width != this.Width || offScreenBitmap.Height != this.Height)

        offScreenBitmap = new System.Drawing.Bitmap(this.Width, this.Height);

 

    // vytvoření objektu Graphics

    Graphics offScreenGraphics = Graphics.FromImage(offScreenBitmap);

 

    // definice posunutých oblastí (vysvětlení pod funkcí)

    Rectangle leftShiftedRectangle = new Rectangle(0, 0, currentPosition, this.Height);

    Rectangle rightShiftedRectangle = new Rectangle(currentPosition, 0, this.Width - currentPosition, this.Height);

 

    // vykreslení obou bitmap podle posunu do paměti

    offScreenGraphics.DrawImage(gradientBitmap, leftShiftedRectangle, this.Width - currentPosition, 0, currentPosition, this.Height, GraphicsUnit.Pixel, new System.Drawing.Imaging.ImageAttributes());

    offScreenGraphics.DrawImage(gradientBitmap, rightShiftedRectangle, 0, 0, this.Width - currentPosition, offScreenBitmap.Height, GraphicsUnit.Pixel, new System.Drawing.Imaging.ImageAttributes());

 

    // posunutí aktuální pozice

    currentPosition = currentPosition + SHIFT_VALUE;

 

    // v přílpadě že hodnota posunu přesáhne šířku ovládacího prvku vynuluj hodnotu pusunu

    if (currentPosition > this.Width)

        currentPosition = 0;

 

    // vykreslení bitmapy z paměti na obrazovku

    pe.Graphics.DrawImage(offScreenBitmap, 0, 0);

}

 

Tady dlužím vysvětlení ohledně posunutých bitmap. Řešil jsem vykreslení tak, že jsem si nakreslil do paměti dvě stejné bitmapy o poloviční šířce, které se liší pouze orientací barevného přechodu. Když bych tyto bitmapy přiložil k sobě, dostanu bitmapu která odpovídá tomu, co potřebuji vykreslit v základním postavení. Každý posun se řeší tak, že zkopíruji oblast této celkové bitmapy a vložím ji do paměťové bitmapy posunutou o požadovanou hodnotu. Debugováním tohoto příkladu sami zjistíte, jak tento ovládací prvek pracuje.

Nyní už jen zbývá vytvořit funkce, které zajistí start a zastavení animace tohoto indikátoru.

 

// Start animace

public void Start()

{

    // inicializace nového objektu Timer 

    indicatorTimer = new Timer();

 

    // nastavení handleru

    indicatorTimer.Tick += new EventHandler(indicatorTimer_Tick);

 

    // nastavení timeoutu, zde je to čtvrt vteřiny

    indicatorTimer.Interval = 250;

 

    // spuštění timeru

    indicatorTimer.Enabled = true;

}

 

// zastavení animace

public void Stop()

{

    // zastavení timeru a jeho likvidace

    indicatorTimer.Enabled = false;

    indicatorTimer.Dispose();

}

 

// Readonly property přes kterou můžeme zjistit zda je animace spuštěná

public bool IsRunning

{

    get

    {

        if (indicatorTimer == null)

            return false;

        return indicatorTimer.Enabled;

    }

}

 

// timer handler

void indicatorTimer_Tick(object sender, EventArgs e)

{

 

    // tato metoda vyvolá OnPaint() event

    this.Refresh();

}

 

Tak toto je vše. Pro pořádek ještě uvedu sekci kterou vygeneroval designer

 

private System.ComponentModel.IContainer components = null;

 

protected override void Dispose(bool disposing)

{

    if (disposing && (components != null))

    {

        components.Dispose();

    }

    base.Dispose(disposing);

}

 

private void InitializeComponent()

{

    components = new System.ComponentModel.Container();

}

 

 a nyní už nezbývá než obládací prvek zkompilovat do DLL knihovny a pak jej přidat do Toolboxu Visual Studia.

Tohle byl ovládací prvek LivingIndicator krok za krokem. V mé verzi umí LivingIndicator zobrazovat ještě libovolný text přes běžící indikátor, ale to už je na vás, jak si jej vylepšíte. Samotný kód jistě není úplně dokonalý. Některé části jsem rozvedl pro větší srozumitelnost a naopak, zachytáváním případných vyjímek jsem se moc nezabýval. Holt, doba je rychlá a času málo. Pokud byste měli nějaké dotazy či připomínky, rád je uvítám.

Tím končím, tvá Máňa. Smile

 

Posted: 7. února 2007 17:40 by MirekE
Vedeno pod:

Komentář

cvalik napsal:

tiez po ukazkach MS sme sa rozhodli ze vybudujeme aplikaciu na zber dat. Avsak po nasadeni do ostrej prevadzky (5000 zaznamov) a plnenia (ulozenie 600) zaznamov zistujeme ze je to nepouzitelne a STRESNEEEE POMALEEEEEEE.....

# února 8, 2007 12:33

MirekE napsal:

Píšu aplikaci pro firmu, která má v portfoliu 8000 položek. V DB je něco kolem 12.000 záznamů. Je to prodejní aplikace, kdy po vytvoření objednávek se data posílají přes GPRS do firmy a stahují se nová data... z počátku jsem se potýkal se stejným problémem rychlosti, ale pokud člověk začne opravdu programovat a nee využívat jen to co je někde v příkladech, dá se napsat aplikace se kterou se dá pracovat. Je svižná, má vlastní grafické rozhraní. Jedná se hlavně o caching dat, statické preloaded tables, vlastní list controls které umí pagging a pod.

Moje aplikace umí přes bluetooth připojeným scannerem čárového kódu vyhledat položky podle EANu, umí přes blutooth tisknout objednávky. Kompletní replikaci, tj úplně novou DB zreplikuje za cca 5 min. denní replikace kdy se přenáší cca 800 záznamů je trvá kolem 20 vteřin.

Co se týče rychlosti prostředí, opravdu se jedná o takřka hodinářskou práci s pamětí, jejím uvolňováním, šetřením, správnou práci s daty a pod.

# února 8, 2007 14:21
Neregistrovaní uživatele nemužou přidávat komentáře.
Vyvojar.cz na prodej!