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

C# Projects

Malá řešení pro velké projekty.
GraphicButton: .NET Compact Framework Control - jak to nefunguje

V minulém příspěvku jsem popisoval tvorbu vlastního ovládacího prvku, 'GraphicButton'. Ve svém obchodním řešení jsem potřeboval mnoho takových ovládacích prvků a když si představíte, že pro každý typ musíte vytvářet tři obrázky (pro každý stav jeden), přestává takové psaní být zábavou a stává se otročinou, při které otevíráte obrázek ve Photoshopu, převádíte jej do černobílé (stav 'disabled'), pak opět otevíráte a zjasňujete barvy a zdůrazňujete světla (stav 'pressed'). Zkrátka zkusil jsem metodu, se kterou bych vás rád seznámil a varoval vás před ní. Tudy skutečně cesta nevede.

Myšlenka byla taková, že předhodím ovládacímu prvku jeden obrázek a on sám si vytvoří stavové ekvivalenty, tedy černobílý pro stav 'disabled' a barevně rozlišený pro stav 'pressed'. Vše se mělo odehrávat ve chvíli, kdy jste vlastnosti Image přiřadili vámi vybraný obrázek.

 

    public Image Image

    {

        get { return _Image; }

        set {               

            _Image = value;

            _DisabledImage = CreateDisabledImage(value, this._TransparentColor);

            _PressedImage = CreatePressedImage(value, this._TransparentColor);

        }

    }

 

Názvy funkcí jasně hovoří pro co která je použita a tak tedy zbývá jen ukázat jak vypadají. Popis hledejte přímo v kódu.

 

   

    private static Image CreateDisabledImage(Image image, Color transparentColor)

    {

        // vytvoření bitmapy z obrázku

        Bitmap disabledBitmap = new Bitmap(image);

 

        // pro každý bod v bitmapě

        for (int x = 0; x < disabledBitmap.Width; x++)

        {

            for (int y = 0; y < disabledBitmap.Height; y++)

            {

                // barva bodu

                Color oldColor = disabledBitmap.GetPixel(x, y);

 

                //pokud se jedná o transparentní barvu, ignoruj bod

                if (oldColor != transparentColor)

                {

                    // zprůměruj hodnoty složek barvy bodu

                    int averageColor = (oldColor.R + oldColor.G + oldColor.B) / 3;

 

                    // vytvoř šedou barvu a nastav barvu bodu v bitmapě

                    Color newColor = Color.FromArgb(averageColor, averageColor, averageColor);

                    disabledBitmap.SetPixel(x, y, newColor);

                }

            }

        }

        // vrať obrázek převedený do odstínů šedé

        return (Image)disabledBitmap;

    }

 

    private static Image CreatePressedImage(Image image, Color transparentColor)

    {

        // vytvoření bitmapy z obrázku

        Bitmap pressedBitmap = new Bitmap(image);

 

        // pro každý bod v bitmapě

        for (int x = 0; x < pressedBitmap.Width; x++)

        {

            for (int y = 0; y < pressedBitmap.Height; y++)

            {

                // barva bodu

                Color oldColor = pressedBitmap.GetPixel(x, y);

 

                //pokud se jedná o transparentní barvu, ignoruj bod

                if (oldColor != transparentColor)

                {

                    // ztmav hodnoty složek barvy bodu

                    int newR = (oldColor.R - 20) < 0 ? 0 : (oldColor.R - 20);

                    int newG = (oldColor.G - 20) < 0 ? 0 : (oldColor.G - 20);

                    int newB = (oldColor.B - 20) < 0 ? 0 : (oldColor.B - 20);

 

                    // vytvoř novou barvu a nastav barvu bodu v bitmapě

                    Color newColor = Color.FromArgb(newR, newG, newB);

                    pressedBitmap.SetPixel(x, y, newColor);

                }

            }

        }

        // vrať obrázek převedený do odstínů šedé

        return (Image)pressedBitmap;

    }

 

Myslel jsem si, že tohle není až tak špatná metoda. V podstatě funguje, jenže... má to jedno velmi podstatné jenže. Každá z těchto metod si na celkem výkonném zařízení DELL Axim X51v vezme okolo šesti vteřin. Určitě se budopu časy lišit dle toho, jak veliké obrázky použijete, ale s obrázky o standardní velikosti ikon 32x32 bodů si každá z nich vzala těch 6 vteřin. Tedy dohromady 12 vteřin!!!

Když si představím, že takových tlačítek použiji ve formuláři třeba pět, tak jen inicializací tlačítek při otevírání formuláře ztratím jednu minutu, což je neúnosné. Tedy závěr zní: 'Tudy cesta nevede!'

Nicméně to nevzdávám a pracuji na řešení, které využije ImageAttributes právě pro vytvoření bitmap pro jednotlivé stavy. Uvidíme, zda se zadaří :)

Mirek

P.S.: tak pomocí ImageAttributes to také nezkoušejte. V .NET Compact Framework není jiná možné apllikovat matrici, takže bohužel, ani tímto způsobem ne. Nezbývá než zůstat prozatím vytváření třech bitmap.

GraphicButton: .NET Compact Framework Control

'Nobody is perfect' zaznělo kdysi v jednom slavném americkém filmu. Platí to stále a zaplať pánbuh za to. Nedokonalosti jsou základem pro různorodost a krásu... asi bych měl přestat s filozofií a začít s tím, proč jsem tady dnes. Windows Mobile 5 a .NET Compact Framework 2.0. Tak jako každému vývojáři pro tuto platformu, i mně začal chybět ovládací prvek, který bych pojmenoval stejně jako mnozí přede mnou 'GraphicButton'. Tedy tlačítko, které je schopné zobrazovat místo textu bitmapu.

K vytvoření takového talčítka musíme udělat jen několik věcí:

  • vytvořit třídu která bude poděděna z bázové třídy Control
  • nadefinovat vlastnosti typu Image pro každý stav tlačítka
  • přepsat metodu OnPaint()

Toto jsou v zásadě tři věci, které je potřeba udělat. Zní to jednoduše a věřte, že to skutečně jednoduché je.

V první řadě tedy vytvoříme třídu GraphicButton, která bude poděděna z třídy Control.

   

    public class GraphicsButton : Control

    {

        /// <summary>

        /// Required designer variable.

        /// </summary>

        private System.ComponentModel.IContainer components = null;

 

        public GraphicsButton()

        {

            InitializeComponent();

        }

 

        /// <summary>

        /// Clean up any resources being used.

        /// </summary>

        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>

        protected override void Dispose(bool disposing)

        {

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

            {

                components.Dispose();

            }

            base.Dispose(disposing);

        }

 

        #region Component Designer generated code

 

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {

            this.SuspendLayout();

            //

            // GraphicsButton

            //

            this.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Bold);

            this.Size = new System.Drawing.Size(20, 20);

            this.ResumeLayout(false);

 

        }

        #endregion

    }


 

V podstatě stačí, abyste si vytvořili novou třídu poděděnou z bázové třídy Control a vše ostatní zařídí designer VS 2005. Nyní je zde pár vlastností, které této třídě nemůžeme upřít. Použití těchto vlastností je popsáno pod blokem kódu.      

 

        #region Private properties

        // Graphic environment properties

        private Rectangle clientRectangle;

        private Bitmap offScreenBitmap = null;

 

        // Internal status of the button

        private bool isPressed = false;

 

        // Transparent color

        private Color _TransparentColor = Color.Magenta;

        private System.Drawing.Imaging.ImageAttributes imageAttributes = null;

 

        // Button images

        private Image _Image = null;

        private Image _PressedImage = null;

        private Image _DisabledImage = null;

 

        #endregion

 

        #region Public properties

        public Image Image

        {

            get { return _Image; }

            set { _Image = value; }

        }

        public Image DisabledImage

        {

            get { return this._DisabledImage; }

            set { this._DisabledImage = value; }

        }

        public Image PressedImage

        {

            get { return this._PressedImage; }

            set { this._PressedImage = value; }

        }

 

        public Color TransparentColor

        {

            get { return _TransparentColor; }

            set { _TransparentColor = value; }

        }

        #endregion

 

Rectangle clientRectangle 
slouží k definování oblasti vykreslování našeho ovládacího prvku

Bitmap offScreenBitmap
do této bitmapy která je přítomna pouze v paměti nejprve vykreslíme ovládací prvek a pak jej celý vykreslíme na obrazovku. Je to jeden ze způsobů jak zabránit tomu, aby nám ovládací prvek poblikával, nebo aby bylo patrné jeho postupné vykreslování.

bool isPressed
stavová proměnná, která nás iformuje o tom, zda se talčítko nachází ve stavu stisknuto, či nikoli

Color _TransparentColor
zde definujme barvu, kterou si přejeme zobrazit jako transparentní. To nám umožní vytvářet tlačítka i jiných tvarů než jen klasická obdélníková či čtvercová.

ImageAttributes imageAttributes
tyto atributy budou použity při vytváření bitmapy právě pro zobrazení transparentní barvy.

Image _Image
základní bitmapa tlačítka

Image _PressedImage
bitmapa pro stisknuté tlačítko

Image _DisabledImage
bitmapa pro disablované tlačítko

Jako veřejné pak samozřejmě definujeme pouze ty vlastnosti, u kterých chceme aby byly viditelné mimo třídu.

Pokud máme takto připravenou třídu, potřebujeme pár pomocných funkcí. První z nich vytvoří objekty GDI+, jako jsou atributy s nastavenou transparentní barvou a oblast vykreslení našeho ovládacího prvku.

 

    private void CreateGDIObjects()

    {

        clientRectangle = new Rectangle(0, 0, this.ClientRectangle.Width,  

                                                                 this.ClientRectangle.Height);

        if (imageAttributes == null)

            imageAttributes = new System.Drawing.Imaging.ImageAttributes();

 

        imageAttributes.SetColorKey(this._TransparentColor, this._TransparentColor);

    }

 

 Další funkcí je pak funkce, která zajistí vytvoření tzv. 'offscreenBitmap'. Jak název napovídá, jedná se obitmapu, která není přímo na display zařízení, nýbrž je určená pro práci v paměti. Tato funkce se volá pouze v případě, že se změnila velikost ovládacího prvku tak, abysme si vytvořili 'paměťovou bitmapu' odpovídající velikosti.

 

    public static void CreateOffScreenBitmap(ref System.Drawing.Bitmap offScreenBitmap,  

                                             System.Windows.Forms.Control control)

    {

            // Only create if don't have one and the size hasn't changed

            if (offScreenBitmap == null || offScreenBitmap.Width != control.Width || 

                offScreenBitmap.Height != control.Height)

            {

 

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

            }

    }

 

Následující 'overriden' metody zajišťují změny stavové proměnné a volání metody Refresh pro překreslení ovládacího prvku.

 

        protected override void OnMouseDown(MouseEventArgs e)

        {

            this.isPressed = true;

            this.Refresh();

            base.OnMouseDown(e);

 

        }

        protected override void OnMouseUp(MouseEventArgs e)

        {

            this.isPressed = false;

            this.Refresh();

            base.OnMouseUp(e);

        }

 

        protected override void OnResize(EventArgs e)

        {

            base.OnResize(e);

            this.Refresh();

        }

 

A dostáváme se k poslední, avšak nejdůležitější metodě a tou je metoda OnPaint. Tato metoda zajišťuje vykreslení našeho ovládacího prvku. Popis jednotlivých kroků naleznete přímo v kódu.

 

    protected override void OnPaint(PaintEventArgs pe) 

    {

        // Příprava prostřední - vytvořenéí GDI+ objektů a paměťové bitmapy

        CreateGDIObjects();

        CreateOffScreenBitmap(ref offScreenBitmap, this);

        Graphics offScreenGraphics = Graphics.FromImage(offScreenBitmap);

 

        // smazání oblasti - barvou pozadí prvku

        offScreenGraphics.Clear(this.BackColor);

 

        // Pokud není deklarována bitmapa, vypiš text - veškeré vykreslování se porovádí do

           paměťové bitmapy

        if (this._Image != null)

        {

            if (this.Enabled)

            {

                if (this.isPressed && this._PressedImage != null)

                    offScreenGraphics.DrawImage(this._PressedImage, clientRectangle, 0, 0, _PressedImage.Width, _PressedImage.Height, GraphicsUnit.Pixel, imageAttributes);

 

                if (!this.isPressed)

                    offScreenGraphics.DrawImage(this._Image, clientRectangle, 0, 0, _Image.Width, _Image.Height, GraphicsUnit.Pixel, imageAttributes);

            }

            else

            {

                if (this._DisabledImage != null)

                    offScreenGraphics.DrawImage(this._DisabledImage, clientRectangle, 0, 0, _DisabledImage.Width, _DisabledImage.Height, GraphicsUnit.Pixel, imageAttributes);

            }

        }

        else

        {

            SizeF stringSize = offScreenGraphics.MeasureString(this.Text, this.Font);

            float stringPosX = (clientRectangle.Width / 2) - (stringSize.Width / 2);

            float stringPosY = (clientRectangle.Height / 2) - (stringSize.Height / 2);

 

            offScreenGraphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), stringPosX, stringPosY);

        }

 

        // vykresli paměťovou bitmapu na pozici ovládacího prvku

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

    }

 

Poslední metodou kterou doporučuji implementovat při každé tvorbě vlastního ovládacího prvku je metoda OnPaintBackground. Tato metoda která neobsahuje v těle žádný kód zajistí, že váš ovládací prvek nebude poblikávat při každém překreslování formuláře na kterém je umístěn.

   

    protected override void OnPaintBackground(PaintEventArgs e) {  }

 

Nyní už jen stačí kód zkompilovat do knihovny a tu pak naimportovat do panelu nástrojů ve VS 2005.

Po pravdě řečeno jsem původně chtěl psát o tom, jak tuhle věc nedělat, ale přišlo mi hloupé začít bez toho, aniž bych to základní. Tedy příště se podíváme na to, jak se nepokoušet počítat stavové bitmapy pro vlastní ovládací prvek a naopak jak to by to jít mohlo. Vlastně budeme hledat řešení, jak se zbavit nutnosti vytvářet bitmapu pro každý stav jednu a zkusíme tlačítku podstrčit jen jednu a nechat ho dopočítat bitmapy pro stav 'pressed' i pro 'disabled'.

Pro dnes je to vše.

Mirek

 

SSIS OLE DB Destination versus AS400 DB2

Při svém bádání nad možným i nemožným propojování světů a galaxií jsem narazil na problém, který myslím stojí za to uveřejnit a podělit se s vámi. Již (dovolím si říci) spolehlivě fungující technologie OLE DB přináší občas mnohá překvapení v podobě chybových hlášení, jejichž popis nenajdete nikde na internetu a to ani na stránkách výrobců a vývojářů propojovaných technologií. Můj případ byl naštěstí vyjímkou a řešení svého problému jsem našel (sice až úplně dole v pomyslné hromadě informací) na internetu v nějakém velmi zastrčeném vlákně jakési diskuzní skupiny.

A teď již k samotnému problému. Jedná se o použítí Data Flow Component, konkrétně OLE DB Destination adaptéru v SLQ Server Integration Services (SSIS). Tak jako užívám OLE DB Source komponentu pro pumpování dat z tabulek IBM AS400 DB2 stroje, chtěl jsem použít pro zpáteční cestu logicky OLE DB Destination adaptér. Vytvořil jsem tedy nové Native OLE DB připojení a jako poskytovatele jsem použil 'IBM DB2 UDB for iSeries IBMDA400 OLE DB Provider'. Je to ten samý provider kterého používám tahání dat z AS400 do SQL Serveru 2005. Bohužel, po té co jsem nakonfiguroval připojení, ověřil, že test spojení proběhl, komponenta při své validaci nahlásí chybu:

TITLE: Microsoft Visual Studio
------------------------------
Error at Export Product Monitoring to AS400 [OLE DB Destination [526]]: SSIS Error Code DTS_E_OLEDBERROR.  An OLE DB error has occurred. Error code: 0x80004005. An OLE DB record is available.  Source: "IBMDA400 File Rowset"  Hresult: 0x80004005  Description: "CPF4328: Member ROI13P not journaled to journal *N.".
Error at Export Product Monitoring to AS400 [OLE DB Destination [526]]: Opening a rowset for "A73ROE.ROI16P" failed. Check that the object exists in the database.
------------------------------
ADDITIONAL INFORMATION: Exception from HRESULT: 0xC02020E8 (Microsoft.SqlServer.DTSPipelineWrap)
------------------------------

Zlí jazykové tvrdí, že jediná možnost jak toto vyřešit je ta, že zapnete 'journaling' tabulky ke které přistupujete na AS400. Komponenta totiž požaduje transakční zpracování a to bez zapnutého 'journalingu' na straně AS400 nelze. Alespoň takové vysvětlení jsem dostal. Navíc administrátoři těchto systémů k přistupují k zapínání 'journalingu' velmi neradi, už proto, že tzv. 'journaling' je neskutečný požírač prostoru a i když nastavíte velikosti souborů kam se se změny ukládají, musíte nějakým způsobem zajistit postupné odmazávání starších verzí těchto odkládacích ('journalovacích') souborů, protože systém po dosažení velikosti souboru tento ponechá a vytvoří nový. Nedochází tedy k přepisu starého souboru.

Řešení které zde popisuji nevyžaduje vůbec žádný 'journaling' ani jinou podobnou úchylárnu na straně 'Velké Modré'. Stačí jen, když místo zmiňovaného a na OLE DB Source komponentách úspěšně používaného poskytovatele s názvem 'IBM DB2 UDB for iSeries IBMDA400 OLE DB Provider' použijete jiného, který se též nachází pod skupinou Native OLE DB\SQL Native Client a jmenuje se (pozor!!" skoro stejně) 'IBM DB2 UDB for iSeries IBMDASQL OLE DB Provider'. V tuto chvíli vám již OLE DB Destination komponenta zafunguje bez zmiňované chyby.

 P.S: Není to buhví jaké řešení, ale je to řešení. Pokud by někdo z vás měl  zkušennosti s psaním vlastních SSIS komponent (já jsem již nějaké napsal, ale občas by se mi šikla nejaká ta konzultace) budu rád když se ozvete.

Tak se zatím mějte krásně.

 Mirek

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

 

Jak jsem přišel zase jednou s křížkem po funuse.

Není tomu dávno, co mě jakási náhoda zavedla na stránky vyvojar.cz. Možná byly dokonale skryty před mými zraky, možná málo křičely do světa, těžko říci. Každopádně ihned po mém příchodu jsem si všiml zajímavého příspěvku, který pojednával o soutěži 'Komponenta roku'. Tento nezůstal mnou bez povšimnutí, stejně tak jako odkazy na stránky tvůrců komponent a hodnocení porotců. I povzdechl jsem si, že je škoda že jsem to nevěděl dříve, protože bych se jistě také zůčastnil a docela jistě bych se umístil. Vím, že mi rozumíte, neboť ego každého programátora jest věcí velikou a nepopiratelnou a tedy dobře chápete, proč říkám, že bych se 'jistě umístil'. Neúspěch je možný pouze v přípdě, že porotci jsou hloupí, nedosahují mých výšin a v životě neviděli pořádně napsanou komponentu, natož aby sami nějakou vyplodili Smile

(Tak jsem zvědavý, zda existují méně chápaví a zda dostanu za tento příspěvek, resp jeho úvod řádný céres.)

Nicméně zpět k soutěži již proběhnuvší. Všechna řešení byla velmi zajímavá nicméně jedno mě zaujalo více než ta ostatní. Jednalo se o komponentu CanvasControl. Proč mě tolik zaujala? Protože řešila v podstatě to samé, co jsem kdysi řešil já. Generování obrázku do stránky bez nutnosti jeho existence. V mém případě se jednalo o vygenerovaný histogram na základě zadané URL fotografie.

Pro ty z vás, kteří nevědí co je to histogram: jedná se o grafické znázornění úrovní jednotlivých odstínů barev ve fotografii.
Ti z vás, kteří fotografují a občas pošlou svou fotografii do nějaké té webové galerie, která je mimochodem plná kritiků, kteří hodnotí vaše dílo co do kvality kompoziční, technické tak i  emocionální dobře vědí, že termín 'histogram' je velmi oblíben právě u těchto kritiků. Proč? Protože histogram neukecáte. Můžete tvrdit, že dotyčný kritik má špatně seřízený monitor, když nevidí kresbu v nejtmavších místech vaší fotografie, nebo když kritizuje 'vypálená' místa na lících vaší milé která vám stála modelem, ale stačí aby se člověk podíval na histogram té fotografie a má jasno.

Takže toliko k histogramu. A právě pro takové galerie, nebo pro takové kritiky, (potažmo pro sebe) jsem kdysi napsal tuto komponentu. Bohužel se mi ji nepodařilo rozeběhnout na svém venkovním webhostingu. Na mém vlastním IIS běhá, ale u mého providera ne. Zkusil jsem použít GenericHandler, ale bez výsledku. Zatím zjišťuji, jestli je ashx zpracováván standardní cestou, případně jestli mu nevadí nějaká knihovna kterou jsem použil. Možná že by mohl být problém v knihovně System.Net, která asi není na webhostingu trusted a já ji užívám pro vytvoření HttpWebRequestu pro získání streamu vzdáleného obrázku. Až si vyšťourm řešení z nosu, dám vědět a řeknete mi, zda by měla taková komponenta šanci na umístění (Tedy pokud nejste hloupí) Wink

Pitomoučký .NET Remoting

Mnoho technologií nedojde své slávy jen díky tomu, že od začátku působí na začínající programátory velmi robustně, nabubřele a velmi neprolomitelně. .NET Remoting skutečně je technologií robustní alespoň co se výkonu týče. Za nabubřelý bych jej nepovažoval, ale to možná jen proto, že jsme mu již pod sukně nakouknul a tak trošku jsem poznal, že je to velmi příjemný kámoš. Na pivo s ním sice nepůjdu (pokud se nepočítá pročítání bible remotingu pod názvem .NET Advanced Remoting ve smíchovské pivnici Na Verandách), ale jakmile potřebuji software, který musí komunikovat s jiným software ať už v podobě Windows nebo Web Services, ten 'zmetek' Remoting tam chybět nesmí. Připravil jsem pro ty, kteří se zatím ostýchají vstoupit do těchto vod takový Pitomoučký .NET Remoting příklad. Tak jdeme na to.

Bez trochy teorie to nepůjde takže ve zkratce:

otázka: Jak donutím dva programy, aby si spolu vyměňovali informace?

odpověď: Stav jednoho programu budu ukládat do souboru a druhým programem je budu číst.......????!!!!!!...  Tak tahle odpověď, ta snad patří do blogu pro programátory BAT souborů :)
odpověď: napíšu vlastní TCP Client a TCP Server aplikaci.... ????!!!!!...... Jistě, i tohle může být cesta a někdy jediná správná, ale tohle jsem zrovna slyšet nechtěl.
odpověď: použiju technologii .NET Remoting ..... !!!!! Anoooooo. Správně.

Tak již nechám pokusu o oživení a půjdu dál. Takže Technologie .NET Remoting umožňuje dvěma programům spolu komnunikovat. K tomu aby dva programy spolu dokázali komunikovat, potřebujeme tři objekty:

  • ServerApplication
  • ClientApplication
  • RemoteObject

Jeden bude sedět na serveru, druhý bude dřímat v klientské aplikaci a třetí bude objektem, který budou tyto dva objekty sdílet, resp přes který si obě aplikace budou vyměňovat informace.

Poznámka: Zde bych chtěl upozornit hnidopichy kterým chybí přesné definice a obsáhlé 'nabubřelé' odstavce, že tento příspěvek je pro jejich méně zdatné kolegy, kteří neví jak se rozhoupat. Stick out tongue

Serverová aplikace může být například Windows Service, ale my si vytvoříme aplikaci, kterou spustíme hezky růčo, abysme si nekomplikovali život. Tedy bude to Windows Console aplikace. Klientská aplikace bude aplikací též konzolovou a taktéž jen pro zjednodušení příkladu.

RemoteObject

Začneme objektem, který je nejzajímavější z celé této trojice a tím je RemoteObject, tedy ten posel dobrých i špatných zpráv, zpráv které si vyměňuje Server s Clientem.

 

using System;

 

namespace RemotingTest

{

    public class RemoteObject : MarshalByRefObject

    {

        // private property

        private int _Counter = 0;

 

        // public property

        public int Counter

        {

            get { return _Counter; }

        }

 

        // metoda zvyšující hodnotu proměnné _Counter

        public void InceraseCounter()

        {

            _Counter++;

        }

 

        // překrytí metody bázové třídy která určuje životnost instance objektu

        public override object InitializeLifetimeService()

        {

            return null;

        }

    }

}

 

Tak toto je náš pitomoučký RemoteObject. Hned první věc která zaujme je bázová třída od které je náš RemoteObject odvozen. Jedná se o třídu která umožňuje objektům komunikaci mezi aplikačními doménami právě v aplikacích kde je použita technologie .NET Remoting. Více pro tuto chvíli netřeba vědět. Přepsáním metody InitializeLifetimeService() bázové třídy zajistíme, že instance objektu hned tak nevyprší. Každá taková instance v závislosti způsobu jejího vytvoření má totiž svou životnost a abychom zajistili, že nám nezmizí dríve než ji budeme chtít v našem příkladu použít, přepíšeme tuto metodu tak jak je popsáno výše. Tento objekt nakonec zkompilujeme do samostatné knihovny např: RemoteObject.dll (kupodivu)

Nyní se podíváme na ServerApplication

 

using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

 

namespace RemotingTest

{

    class Program

    {

        static void Main(string[] args)

        {

            // Vytvoření instance tcp komunikačního kanálu.

            TcpChannel tcpChannel = new TcpChannel(2222);

 

            // zaregistrování kanálu

            ChannelServices.RegisterChannel(tcpChannel, false);

 

            //vytvoření instance RemoteObject

            RemoteObject rObject = new RemoteObject();

 

            // Nabídnutí RemoteObject instance pro okolí na adrese 'tcp://jmenopocitace:2222/RemoteObject.rem'

            ObjRef oRef = RemotingServices.Marshal(rObject, "RemoteObject.rem");

 

            // smyčka pro ovládání serverové části - při stisku klávesy se navýši hodnota proměnné RemoteObject.Counter

            while (true)

            {

                Console.WriteLine("Server Counter:{0}", rObject.Counter);

                Console.ReadKey(true);

                rObject.InceraseCounter();

            }

        }

    }

}

 

Tak a to je celá ServerApplication. Není to tak složité že? Komunikační kanál, registrace, instance objektu a jeho vystavení blízkému i vzdálenému okolí. Vězte však, že jakmile se začnete zabývat .NET Remotingem hlouběji, zjistíte, že toho dokáže o dost více. Dozvíte se mimo jiné, že nemusíte komunikovat například jen po TCP kanálu, ale že to jde i po HTTP a spoustu dalšího. Znovu připomínám, že se jedná o 'Pitomoučký příklad', který má pomoci rozhoupat se těm, kteří to ještě nezkusili. 

No a zbývá už jen ClientApplication

 

using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

 

namespace RemotingTest

{

    class Program

    {

        static void Main(string[] args)

        {

            // Vytvoření prázdné instance komunikačního kanálu

            TcpChannel tcpChannel = new TcpChannel();

 

            // Jeho zaregistrování

            ChannelServices.RegisterChannel(tcpChannel, false);

 

            // žádost o instanci RemoteObject která byl vytvořena Serverpplication a čeká v serverové aplikaci.

            RemoteObject rObject = (RemoteObject) Activator.GetObject(typeof(RemoteObject), "tcp://localhost:2222/RemoteObject.rem");

 

            // cyklus pro ovládání programu - s každým průchodem ClientApplication přečte hodnotu

            // která je v RemoteObject.Counter jehož instance byla vytvořena v ServerApplication

            while (true)

            {

                Console.WriteLine("Server Counter on Client:{0}", rObject.Counter);

                Console.ReadKey(false);

            }

        }

    }

}

 

Klientská aplikace také není žádná románová kapitola. Povšimněte si, že u klientské aplikace nezadáváme při inicializaci TCP kanálu port na kterém má komunikovat. Mohli bychom, ale v případě že budeme pouštět obě aplikace na stejném stroji pak nesmíme chtít komunikovat po stejném portu jako serverová aplikace. Prostě po stejném portu to nepůjde. Navíc se jedná o klientskou aplikaci a tam je nám jedno jaký port bude v užití. Konstrukce kterou jsem použil prostě vybere 'nějaký volný port'. Port který jsme nastavili na serverové aplikaci nás zajímá až ve chvíli, kdy žádáme o instanci RemoteObject. To je port, na kterém serverová aplikace naslouchá a kde vyřídí náš požadavek o instanci již vtvořeného objektu.

Pokud jste došli až sem, pak nezbývá než zkompilovat knihovnu a obě aplikace. Nezapomeňte přidat do obou aplikací referenci právě na naší knihovnu, kde se nachází definice objektu RemoteObject.
Po zkompilování pusťte nejprve ServerApplication a poté ClientApplication. Střídavým přepínáním mezi aplikacemi a stiskáváním libovolné klávesy uvidíte, jak klientská aplikace komunikuje se serverovou. V případě stisku klávesy v serverové aplikaci zvýšíte hodnotu _Counter o jednu. Pokud tak učiníte několikrát za sebou a pak přepnete do klientské aplikace, tak tam se vám po stisku klávesy zobrazí skutečně aktuální hodnota proměnné objektu, který se nachází v serverové aplikaci. Konec konců sami dobře víte, že v klientské aplikaci se žádné navyšování této hodnoty neděje, tam poze tuto hodnotu čteme.

Poznámka: Je nutné povolit komunikaci programů ve Windows Firewallu, který většině z vás zobrazí dotaz na púovolení této komunikace. Pokud nezobrazí a program nebude fungovat, vězte, že za to může právě firewall a jeho nastavení

Možná že se budete muset trochu poprat s tímto příkladem, ale věřte, funguje to. Zajímavé to začne být ve chvíli, kdy ServerApplication spustíte na jiném počítači než ClientApplication. V takovém případě bude zjevné, že skutečně dochází ke vzdálené komunikaci mezi oběma aplikacemi. V tom případě neopomeňte opravit url cestu k objektu.

.NET Remoting je opravdu velmi silný nástroj a pokud se vám zdá, že byste pro tuto technologii našli uplatnění ve vašich projektech, doporučuji hlubší studium. Nastudujte si termíny jako je  Singleton, Singlecall, SOAP a pod. Uvidíte, že se vám to vyplatí.

P.S: Závěrem bych chtěl poděkovat chlápkovi s loginem vlko který mě upozornil na ten skvělý Add-In díky kterému se dají zdrojové kódy kopírovat z Visual Studia v tak pěkné formě Yes

 

 

 

 

Nešumět a restartovat

Kruci tohle blogování má něco do sebe... Big Smile člověk má pocit, že nešumí..... Tedy pro nezasvěcené: v seriálu IT Crowds vysvětloval jeden člen IT teamu své nové šéfové, jak pracuje na takovém jednoduchém prográmku... a najednou se jeho povídání o proměnných a pointerech a pointerech na pointery změnilo v šum podobný tomu, který vydává bedna (čti: televize), když nemá žádný signál. Tento šum měl vyjadřovat to, co slyšela šéfová ze slov ochotně vysvětlujícího programátora - bezpochyby právem podezřívaná z toho, že počítačům vůbec, ale vůbec nerozumí Smile

Tedy doufám že nešumím a když už jsem u toho, dodám hned jedno řešení z mých archivů. Tentokrát se jedná o SW restart PocketPC zařízení. Určitě to není k zahození, protože pokud někdy budete psát aplikaci se kterou budou pracovat obchodní zástupci po celé republice a ještě o kus dál na Slovensku, budete se muset chca nechca pustit do programování aplikace pro automatický update vašeho software. A takový automatický update se jen málokdy obejde právě bez funkce na SW reboot zařízení.

takže:

 

using System;

using System.Runtime.InteropServices;

 

namespace Endys.Diagnostics

{

    public class PowerManagement

    {

 

#if PocketPC

 

        public const uint FILE_DEVICE_HAL = 0x00000101;

        public const uint METHOD_BUFFERED = 0;

        public const uint FILE_ANY_ACCESS = 0;

 

        // Import knihovny

        [DllImport("Coredll.dll")]

        public extern static uint KernelIoControl(uint dwIoControlCode, IntPtr lpInBuf, uint nInBufSize, IntPtr lpOutBuf, uint nOutBufSize, ref uint lpBytesReturned);

 

        // Vytvoření hodnoty bitovým posunem

        private static uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access)

        {

            return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);

        }

 

        // Volání funkce KernleIoControl

        public static uint ResetPocketPC()

        {

            uint bytesReturned = 0;

            uint IOCTL_HAL_REBOOT = CTL_CODE(FILE_DEVICE_HAL, 15, METHOD_BUFFERED, FILE_ANY_ACCESS);

            return KernelIoControl(IOCTL_HAL_REBOOT, IntPtr.Zero, 0, IntPtr.Zero, 0, ref bytesReturned);

        }

#endif

 

    }

}

 

Na tomto kódu není nic ke zkoumání. Jedná se o využití standardní funkce KernelIoControl ze systémové knihovny coredll.dll. Budu rád, když vám pomůže tak jako mně.

 

 

 

Proč nezačít serializací?

Přemýšlel jsem nad tím čím začít, když už jsem vpadl do blogátorských kruhů. Po zběžné prohlídce svého archívu kódů, sáhl jsem po kódu, který užívám ve svých projektech dosti často. Jedná se o kód, který ukládá instance objektů jednoduchých typů do XML souborů.

K čemu že je to dobré? Vysvětlím.

Jak známo, v prostředí Visual Studia 2005 lze přidat k Windows Forms projektu soubor nastavení, tzv. 'Application settings'. Tato nastavení jsou vynikajícím nástrojem, až do doby, kdy potřebujete měnit hodnoty které spadají do oblasti (scope) Application. Tyto hodnoty totiž nelze změnit za běhu programu tedy v režimu run-time standardní cestou přes objekt Settings:ApplicationSettingsBase, protože property která je ze scope Application je standardně read-only. Znamená to tedy, že pokud použijete tuto cestu pro nastavení vaší aplikace, pak vám nezbyde nic jiného, než manuálně editovat app.settings.xml a zajistit (například restartem aplikace), aby se tato nastavení aplikovala. V případě, že píšete Windows Service, kde si chcete za běhu služby změnit parametry služby přes příjemný uživatelský interface, musíte si své 'settings' napsat sami. 

Dalším příkazem je objekt, který musíte uložit, protože jeho zpracování není v současné době možné a zároveň nesmíte jeho instanci jen tak zahodit (napadá mě použití při odeslání SMS o stavu zpracování nějaké důležité úlohy, ovšem SMS brána není zrovna dostupná. V tom případě je nejlepším způsobem uložení SMS a pokus o její pozdější odeslání).

V podstatě na tom nic není. Vaše settings budou opět jen třídou v jejíž instanci držet nastavení vaší aplikace. Instance této třídy se pak uloží do XML a toto uložení zajišťují právě dvě funkce, které zde popíšu.

V příkladu budu používat třídu MyAppSettings

public class MyAppSettings {...}
jenž je třídou s mými nastaveními a dále pak statickou třídu nazvanou ObjectStorage

public static class ObjectStorage {...}
ve které budu definovat dvě funkce. Jednu pro uložení objektu do souboru a druhou pro jeho opětovné načtení.

 

[Serializable]

public class MyAppsSettings

{

    // privátní properties

    private int _ID = 0;

    private string _Description = null;

 

    // konstruktor bez parametrů

    public MyAppsSettings() { }

 

    // Public properties

    [XmlElement("ID")]

    public int ID

    {

        get { return this._ID; }

        set { this._ID = value; }

    }

 

    [XmlElement("Description", IsNullable = true)]

    public string Description

    {

        get { return this._Description; }

        set { this._Description = value; }

    }

}

 

Takhle tedy vypadá má třída s vlastnostmi které chci ukládat jako nastavení nějaké své aplikace. Jsou zde tři důležité věci:

1) Atribut třídy [Serializable]: v tomto případě se může zdát zbytečným, neboť tento příklad by fungoval i bez tohoto atributu, nicméně doporučuji naučit se jej používat, protože v případě použití binární serializace, či složitějších konstrukcí byste jej mohli postrádat.

2) Konstruktor bez parametru MyAppSettings(): ač se zdá, že tento konstruktor je naprosto zbytečný, vězte že tomu tak není. Dále v kódu uvidíte, že k instanciování tohoto objektu používám metodu CreateInstance objektu Activator a tato metoda při tomto použití vyžaduje tzv. parameterless constructor.

3) Atributy jednotlivých properties [XmlElement]: v tomto případě také nejsou nezbytně nutné, ale doporučuji je používat, protože právě jimi se dá ovlivnit struktura výsledného XML souboru.

Teď tedy přichází na řadu má třída ObjectStorage a její dvě funkce pro uložení a načtení uložené instance objektu.

 

public static class ObjectStorage

{

 

    // jako parametry zde použijeme cestu k souboru do kterého chceme zapsat, a instanci naší MyAppsSettings třídy

    public static void Save(string path, object instance)

    {

        // zde zjistíme typ objektu, v našem případě to bude hodnota typeof(MyAppsSettings)

        Type serializedType = instance.GetType();

 

        // a vytvoříme instanci objektu XmlSerializer který bude připraven serializovat data daného typu

        XmlSerializer xmlSerializer = new XmlSerializer(serializedType, new XmlRootAttribute(serializedType.FullName));

 

        // zde už jen otevřeme TextWriter s cestou do souboru do kterého budeme zapisovat

        TextWriter writer = new StreamWriter(path);

 

        // a tato funkce zapíše do TextWriteru instanci našeho objektu a následně TextWriter uzavře.

        xmlSerializer.Serialize(writer, instance);

        writer.Close();

    }

 

 

    // jako paramtery použijeme cestu k souboru kde je uložena instance našeho objektu, typ objektu a bool hodnotu která říká,

    // zda máme v případě neexistence vytvořit nový soubor s nastavením

 

    public static object Load(string path, Type objectType, bool createNew)

    {

        // objekt do kterého budemne nahrávat instanci našeho objektu

        object loadedObject = null;

 

        // teto blok zajistí vytvoření nového souboru v případě, že soubor neexistuje a hodnota parametru createNew se rovná 'true'

        if (!File.Exists(path) && createNew)

        {

            // zde se vytvoří nová instance objektu daného typu

            loadedObject = Activator.CreateInstance(objectType);

 

            //použijeme naší funkci Save pro uložení objektu a vrátíme z funkce instanci nově vytvořeného objektu

            ObjectStorage.Save(path, loadedObject);

            return loadedObject;

        }

 

        // v případě že soubor existuje vytvoříme opět instanci XmlSerializeru s parametry které určují, na jaký objekt má být serializer připraven

        XmlSerializer xmlSerializer = new XmlSerializer(objectType, new XmlRootAttribute(objectType.FullName));

 

        // Otevřeme si TextReader pro čtení ze souboru

        TextReader reader = new StreamReader(path);

        try

        {

            // do objektu načteme metodou Deserialize obsah souboru a soubor uzavřeme

            loadedObject = xmlSerializer.Deserialize(reader);

            reader.Close();

        }

        catch (Exception ex) { throw ex; }

 

        // vrátíme načtený objekt

        return loadedObject;

    }

}

 

Obě funkce mají jako návratový typ typeof(object), tedy jakýsi var (dočkáme se v C# 3.0 Wink ) to proto, abyste je mohli užít s jakýmkoli vámi používaným objektem.

A zde je již malá ukázka použití:

 

class Program

{

    static void Main(string[] args)

    {

        MyAppsSettings mySavedSettings = new MyAppsSettings();

        mySavedSettings.ID = 25;

        mySavedSettings.Description = "Toto je úplně nesmyslný popis";

        ObjectStorage.Save(@"C:\Temp\appSettings.xml", mySavedSettings);

 

        MyAppsSettings myLoadedSettings = (MyAppsSettings)ObjectStorage.Load(@"C:\Temp\appSettings.xml", typeof(MyAppsSettings), false);

 

        Console.WriteLine(myLoadedSettings.ID.ToString());

        Console.WriteLine(myLoadedSettings.Description);

        Console.Read();

    }

}

 

Output:

25

Toto je úplně nesmyslný popis

Pokud si otevřete soubor do kterého jste uložili instanci objektu, měli byste vidět toto:

 

<?xml version="1.0" encoding="utf-8"?>

<SerialExample.MyAppsSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <ID>25</ID>

  <Description>Toto je úplně nesmyslný popis</Description>

</SerialExample.MyAppsSettings>

 

Funkce Save() a Load() lze samozřejmě zakomponovat i do samotného objektu MyAppsSettings a to tak, že funkce Load zůstane statická a funkce Save bude ukládat objekt ve kterém se nachází. Použití už nechám na vás.

Tyto funkce, nejsou samospasitelné a ne vše se dá takhle krásně uložit do XML souboru. Stačí abyste jako property své třídy použili složitější datový typ (např generická třída Dictionary<K,T>) a budete mít problémy. Jistě každý z vás ví, že kdo nebude mít problémy, nosí si někde hluboko jméno ISerializable Smile

Tedy co říci závěrem? Je to můj první blok do blogu, buďte tedy schovívaví. Omluvte prosím mou neschopnost užít v celém, příspěvku jednotné jazykové mutace, ale ať jsem se snažil jak jsem se snažil při vymýšlení českých ekvivalentů, něco prostě nejde. Ale to snad konec konců znáte také. 

Vyvojar.cz na prodej!