Folyamatok dokumentálása saját activity designerrel

Az MSDN Kompetencia Központon belül a BME Információtechnológiai Innovációs és Tudásközpontjával együttműködve épp egy olyan e-dokumentumkezelési projekten dolgozunk, ahol a dokumentumok életciklusát Windows Workflow Foundation alapokon definiált folyamatok vezérlik. sok más mellett a Microsoft szerint a WF előnye, hogy a folyamatok a Visual Studio dizájnere segítségével grafikusan megjeleníthetőek, így a kód szinte önmagát dokumentálja.

Na ez pontosan addig igaz, amíg valaki egy kontroll ID-ja alapján tudja, hogy az mit csinál. A VS ugyanis így jeleníti meg a folyamatok minden egyes elemét, az ún. activity-ket:

Egy hosszú nevű activity

Az esetek többségében tehát az sem derül ki, hogy pontosan hogyan is hívják az adott activity-t, az meg aztán pláne nem, hogy mit csinál. Persze fölévihetem az egeret és akkor megjelenik a leírás tooltipben, de azt többek szerint aránylag nehézkes kinyomtatni.

A feladat tehát az, hogy a VS által rajzolt ábrát használható formára kell hozni. Erre a legkézenfekvőbb lehetőség, ha készítünk egy saját activity designer osztályt, ami meghatározza, hogyan jelenjen meg az adott activity a VS tervezőfelületén. A designer osztályhoz egy saját theme osztályt fogunk rendelni, ami pedig azt határozza meg, hogy az adott komponens milyen színekkel, betűtípusokkal, betűméretekkel ábrázolódjon.

Ez idáig aránylag egyszerű, mindössze a designer OnPaint metódusát kell felüldefiniálni úgy, hogy az összes stílus paramétert a theme osztályból vesszük. Ezt meg is lehet csinálni és hamar rá fogunk jönni, hogy a WF tervezői valószínűleg azért rajzoltak minimális activity-ket, hogy egyszerre sok elférjen a VS ablakában. Ha túl kicsi, akkor is használhatatlan, ha túl nagy, akkor is.

Olyan megoldás kellene, hogy tudjuk változtatni, mekkora az activity felülete fejlesztés közben. Ezt meg lehet oldani úgy, hogy kezeljük az egér eseményeket a designerben, de meg lehet oldani úgy is, ahogy ún. composite activity designert készítünk. A composite activity olyan komponens, aminek gyerekei lehetnek, tehát például az IfElse activitynek a lehetséges kimenetei (branchek) a gyermekei. Az ilyen activity-k elég összetettek lehetnek ahhoz, hogy be lehessen csukni és ki lehessen nyitni őket a dizájnerben, aminek a támogatására létezik egy CompositeActivityDesigner ősosztály, amiből mi származtathatunk.

Készítsünk tehát olyan composite activity designert, amit ki lehet nyitni és be lehet csukni fejlesztés közben!

Saját composite activity designer készítése

Kezdjük a témával, abból is összetett kell, származtassunk egyet a CompositeDesignerTheme osztályból és színezzük ki kedvünk szerint:

    public class DemoDesignerTheme : CompositeDesignerTheme
    {
        public DemoDesignerTheme( WorkflowTheme theme )
            : base( theme )
        {
            this.BorderColor = Color.Brown;
            this.BackColorStart = Color.LightYellow;
            this.BackColorEnd = Color.Orange;
            this.BackgroundStyle = LinearGradientMode.Vertical;
            this.ForeColor = Color.Brown;
        }
    }

Ezek után már létrehozhatjuk a designer osztályunkat és hozzácsaphatjuk a témát is. Nagy buzgalommal definiáljuk felül az Initialize metódust, hogy el tudjuk menteni a megrajzolandó activity-re mutató referenciát (erre majd később szükség lesz):

    [ActivityDesignerTheme( typeof( DemoDesignerTheme ) )]
    public class DemoDesigner : CompositeActivityDesigner
    {
        private Activity _activity;

        protected override void Initialize( Activity activity )
        {
            this._activity = activity;
            base.Initialize( activity );
        }
    }

Következő lépésben definiáljuk felül az OnPaint metódust és bányásszuk ki az értelmes dolgokat az esemény paraméterből:

    protected override void OnPaint( ActivityDesignerPaintEventArgs e )
    {
        Graphics g = e.Graphics;
        CompositeDesignerTheme theme = e.DesignerTheme as CompositeDesignerTheme;

        // Tovább is lesz még...
    }

Ott az a csodás Graphics objektum, azon kell kiélnünk rajz tehetségünket. Mindenek előtt talán rajzoljuk meg az activity keretét, még pedig olyan pompás lekerekített formában, ahogy a standard beépített dizájnerek teszik. A bátrabbak megrajzolják a keretet ívekből, a naívabbak még hisznek az MSDN-ben, a gyakorlottabbak pedig Reflectoroznak, és így rátalálnak az ActivityDesignerPaint osztályra. Ennek az osztálynak van néhány publikus metódusa, amelyek közvetlenül használhatóak, de az internalokból is lehet ötleteket meríteni:

    // Keret megrajzolása
    int radius = 4;
    ActivityDesignerPaint.DrawRoundedRectangle( g, theme.BorderPen, this.Bounds, radius - 2 );

Tartsuk fejben, mert a Google-ben mindössze 5 találat van rá! (Talán ezért írta meg kézzel az, aki a Microsoft oldalán lévő WF hands-on labot kitalálta, bár ez sajnos még a kisebb hiba abban a példakódban… smile_confused)

Fessük ki a keretet, de ne azzal a színnel, ami eszünkbe jut, hanem használjuk a téma beállításait:

    // Háttér kifestése
    GraphicsPath path = ActivityDesignerPaint.GetRoundedRectanglePath( this.Bounds, radius );
    g.FillPath( theme.GetBackgroundBrush( this.Bounds ), path );

Mivel az egész OnPaintet felülírjuk, mindenről nekünk kell gondoskodni, így az ikon megjelenítéséről is:

    // Ikon megjelenítése
    ActivityDesignerPaint.DrawImage( g, this.Image, this.ImageRectangle, DesignerContentAlignment.Fill );

És a szöveg kiírásáról is:

    // Szöveg megjelenítése
    ActivityDesignerPaint.DrawText( g, theme.Font, this.Text, this.TextRectangle, 
StringAlignment.Near, e.AmbientTheme.TextQuality, theme.ForegroundBrush );

Itt jön a trükk: rajzoljuk meg a nyitogató gombot, de ezt is bízzuk a profira:

    // Kinyitó ikon megjelenítése
    ActivityDesignerPaint.DrawExpandButton( g, this.ExpandButtonRectangle, !this.Expanded, theme );

Ha idáig eljutottunk, akkor már van egy szép színes, nyomkodható activity-nk, sőt még a jobb egérkettyintős menüben is megjelent az Expand és Collapse parancs:

Saját composite activity

Az activity méretét nyilvánvalóan meg kell változtatnunk, amikor a felhasználó ki szeretné nyitni. Ehhez nem kell egérkattintás eseményt kezelnünk, a VS gondoskodni fog arról, hogy a kattintás után újrarajzolja a teljes activity-t, csak éppen az Expanded tulajdonság értéke fog mást mutatni.

Tűzzük ki célul azt, hogy kinyitott állapotban megjelenítsük az activity Description mezőjének az értékét is, az activity pedig legyen olyan széles, hogy a teljes szöveg tördelés nélkül beleférjen. A méretet az OnLayoutSize metódus felüldefiniálásával tudjuk megadni:

    protected override Size OnLayoutSize( ActivityDesignerLayoutEventArgs e )
    {
        // Meg kell hívni, mert ez inicialize1lja a TextRectangle-t, 
        // amitől az ImageRectangle és az ExpandButtonRectangle is függ
        Size originalSize = base.OnLayoutSize( e );

        string text;
        int height;

        if( this.Expanded )
        {
            // Kinyitott állapotban a Description mérete a mérvadó
            text = this._activity.Description;
            height = originalSize.Height + 30;
        }
        else
        {
            // Becsukott állapotban az activity azonosítójának mérete a mérvadó
            text = this.Text;
            height = originalSize.Height;
        }

        // Az activity mérete a szöveg szélesslgéhez igazodik.
        SizeF s = e.Graphics.MeasureString( text, e.DesignerTheme.Font );
        return new Size( (int) s.Width + 30, height );
    }

A fenti metódusnak van két érdekessége (a MeasureString metóduson kívül persze):

  • Az ikon és a szöveg pozícionálásánál hivatkoztunk az ImageRectangle és a TextRectangle tulajdonságokra, sőt később az ExpandButtonRectangle-re is. Ezeket is felül lehet definiálni, de nem szükséges, mert az activity méretében származtatják magukat, de csak akkor tudják, ha meghívjuk a szülő osztály OnLayoutSize metódusát.
  • Ki lehet próbálni, hogy bármilyen rövid szövegeket adunk meg, az activity nem csökken akármilyen kicsire. Ezt a MinimumSize tulajdonság határozza meg, amit szintén felül lehet definiálni.

Nem maradt más hátra, mint hogy az OnPaintben kinyitott állapotban az ikon alá kiírjuk az activity-hez tartozó Description tulajdonság értékét:

    // Kinyitott állapotban a Description megjelenítése
    if( this.Expanded )
    {
        Rectangle rect = new Rectangle();
        rect.X = this.Bounds.X;
        rect.Y = this.ImageRectangle.Y + this.ImageRectangle.Height + 10;
        rect.Width = this.Bounds.Width;
        rect.Height = this.TextRectangle.Height;

        ActivityDesignerPaint.DrawText( g, theme.Font, this._activity.Description, rect, 
StringAlignment.Center, e.AmbientTheme.TextQuality, theme.ForegroundBrush ); }

Ennél természetesen tovább is lehet menni, például reflection segítségével fel lehet deríteni az activity tulajdonságait és a hozzájuk tartozó attribútumokat és meg lehet jeleníteni azokat is a tervezőben, a nyitott nézetben. Ha kell fejlesztés közben, akkor egy kattintással kinyithatjuk, sőt ki is nyomtathatjuk. Íme tehát a végeredmény összecsukva és kinyitva:

Saját activity összecsukva Saját activity kinyitva

Többekkel beszélgettem már arról, hogy a Windows Workflow Foundation mennyire számít rapid alkalmazásfejlesztési technológiának, azt hiszem ez egy újabb példa arra, hogy nem az.

Az activity teljes forráskódja a devPORTALról tölthető le.

 

Technorati tags: , ,

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s