Otázky a odpovědi

Programátorská poradna

Časová náročnost (min):

Začátečník

Pokročilý

Profesionál

Použitý operační systém : Hlavní vývojový nástroj :

Další vývojový software :

Jiný software :

Windows 2000 SP3

Visual C# .NET 2002

Visual C++ .NET 2002

Žádný

 

 

Dobrý den, chtěl bych vás požádat o pomoc při řešení svého problému. Potřeboval bych z třídy, kterou mám připravenou v řízeném C++, odvodit podtřídu v C# a z konstruktoru této podtřídy zavolat metodu bázové třídy. Můžete mi, prosím, poradit?

 

Poradit vám samozřejmě můžu a také to ihned udělám. Předpokládejme, že kód třídy v řízeném C++ vypadá následovně:

 

Poznámka

Visual C++ .NET se při generování kostry projektu Managed C++ Class Library chová poněkud svérázně. Veškerý programový kód třídy je totiž umístěn do hlavičkového souboru (.h), přičemž odkaz na tento hlavičkový soubor je vložen do implementačního souboru (.cpp). Některým programátorům tento model nemusí vyhovovat, ovšem my budeme dodržovat standardní linii. 

 

//Tento programový kód se nachází v hlavičkovém souboru (.h)

//řízené třídy.

 

#pragma once

//Import metadat z assembly System.Windows.Forms.dll.

#using <System.Windows.Forms.dll>

 

//Import jmenných prostorů.

using namespace System;

using namespace System::Windows::Forms;

 

//Deklarace jmenného prostoru třídy.

namespace RizeneCPP

{

       //Deklarační příkaz řízené třídy s názvem TridaCPP.

       public __gc class TridaCPP

       {

 

             //Veřejně přístupný konstruktor třídy.

             public: TridaCPP()

                           {

                                              

                           }

            

                    // Metoda, která bude volána z kódu jazyka C#.

                    void Metoda(void)

                           {

                                  //Aktivace metody Show třídy MessageBox, které

                                  //je předán řízený textový řetězec znaků.

                                  MessageBox::Show(S"Metoda byla aktivována.");

                           }

       };

}

 

Budete-li chtít dědit z této třídy v C#, můžete použít tento kód:

 

using System;

 

namespace JmennyProstorCS

{

       /// <summary>

       /// Třída je podtřídou třídy vytvořené v řízeném C++.

       /// </summary>

      

       //Deklarační příkaz, jenž vytváří podtřídu z třídy

       //napsané v řízeném C++.

       public class TridaCS : RizeneCPP.TridaCPP

       {

             //Konstruktor třídy.

             public TridaCS()

             {

                    //Použití klíčového slova base, které umožňuje volat

                    //metody bázové třídy z odvozených tříd.

                    base.Metoda();

             }

       }

}

 

Aktivaci metody bázové třídy zabezpečuje klíčové slovo base, za kterým následuje tečka a pojmenování cílové metody, kterou budete chtít zavolat. Aby třída v C# věděla, od jaké třídy je odvozená, je nutné do projektu s podtřídou začlenit referenci na dynamicky linkovanou knihovnu (DLL), v níž je uložen kód řízené třídy napsané v C++.

 

Poznámka

Řízené C++ a také C# podporují pouze jednoduchou dědičnost. To znamená, že není možné vytvořit v C# podtřídu, která by byla odvozena od více než jedné třídy. Implementace jenom jednoduché dědičnosti vychází z požadavku společné jazykové specifikace (Common Language Specification) platformy .NET Framework.

 

Finálním krokem je vytvoření instance podtřídy ve zvoleném zpracovateli události (např. button1_Click):

 

private void button1_Click(object sender, System.EventArgs e)

       {

             JmennyProstorCS.TridaCS Objekt = new JmennyProstorCS.TridaCS();

       }

 

 

Dočetl jsem se, že C# podporuje tzv. boxing, ovšem nevím, co tento termín znamená. Chtěl bych vás proto poprosit o vysvětlení a také programovou ukázku. Děkuji pěkně.

 

Výraz boxing označuje techniku přetypování hodnotového datového typu na objektový protějšek. Povězme, že máme deklarovanou a inicializovanou proměnnou x datového typu int:

 

int x = 100;

 

Typ int je primitivním a současně hodnotovým typem, což znamená, že je schopen uchovávat jenom platné celočíselné hodnoty ze stanoveného intervalu. Když vytvoříte proměnnou typu int, C# vyhradí v paměti počítače dostatek místa pro uložení hodnoty této proměnné. Paměťový prostor, do něhož jsou ukládány hodnoty hodnotových datových typů, se nazývá zásobník (stack). Zásobník je jednoduchá a značně efektivní datová struktura, která pracuje na principu LIFO (Last-In-First-Out, Poslední-Dovnitř-První-Ven). Podle tohoto principu je možné ze zásobníku brát jako první tu hodnotu, která byla na zásobník umístěna jako poslední. Na druhé straně existují referenční datové typy. Proměnné těchto typů, na rozdíl od jejich hodnotových kolegů, neobsahují hodnotu, nýbrž odkaz, neboli referenci, chcete-li. Z technického hlediska představuje odkaz paměťovou adresu. Máme-li tedy odkaz, víme, že na jisté paměťové adrese se nachází nějaká cílová entita. Touto entitou bývá zpravidla objekt. Referenční proměnné jsou zajímavé také z jiného důvodu: Ačkoliv ony samotné jsou ukládány na zásobník, entita, na níž ukazují, se nachází v jiné paměťové oblasti, které se říká řízená hromada (managed heap). Deklarace a inicializace referenční proměnné může mít tuto podobu:

 

Form y = this;  

 

Odkazová proměnná y může obsahovat odkaz na instance (objekty) třídy Form. Důkazem tohoto tvrzení je skutečnost, že proměnná y je okamžitě úspěšně inicializována pomocí klíčového slova this. Klíčové slovo this představuje odkaz na aktuální instanci třídy Form.     

 

Paměťová náročnost proměnných hodnotových typů je poměrně nízká (několik bajtů) a práce s nimi na zásobníků efektivní. V jistých případech by ovšem bylo vhodné, kdyby bylo možné s proměnnou hodnotového typu pracovat jako s proměnnou referenčního typu. A právě tuto situaci řeší boxing.

 

Podstata boxingu spočívá v tom, že proměnnou hodnotového typu „zaboxujeme“. Tuto operaci můžeme provést pomocí tohoto programového kódu:

 

object z = x;

 

Technika boxing probíhá přibližně v těchto krocích:

 

1.      Na řízené hromadě je vytvořen objekt třídy System.Object.

2.      Hodnota proměnné x je nakopírována do tohoto objektu.

3.      Je vrácena paměťová adresa, na které se řízený objekt nachází.

4.      Tato paměťová adresa je uložena do připravené referenční proměnné z.

 

Ano, tento algoritmus je reálný, i když přiznávám, že na první pohled vám může připadat zvláštní. Ovšem, jak je možné uvedené přetypování? Jednoduše proto, že všechny typy, s kterými v rámci .NET Framework Class Library pracujeme, jsou odvozeny od základního typu System.Object. Je-li tomu tak, pomocí indukce dojdeme k poznání, že když jsou všechny typy odvozené od typu System.Object, je možné také všechny typy zpětně přetypovat na tento bázový datový typ. 

 

Obr. 1 – Grafická ilustrace techniky boxing

 

Pro programátory je k dispozici také opačná operace k boxingu, tzv. unboxing.

 

Nakonec dodejme, že s boxingem se můžete střetnou i v jiných programovacích jazycích platformy .NET, mezi které patří Visual Basic .NET či C++ s řízenými rozšířenými (managed extensions).

 

Poznámka

V jazyku C# je boxing realizován implicitně, tedy bez jakéhokoliv zásahu ze strany programátora. Pokud aplikační logika C# rozhodne, že je zapotřebí uskutečnit boxing, stane se tak. Na druhé straně, z výkonového hlediska je boxing zcela jistě méně efektivní jako pouhé umístění proměnné na zásobník. I z tohoto důvodu je boxing v některých jazycích (např. řízené C++) prováděn explicitně (boxing se neuskuteční automaticky, ale sám programátor musí vydat pokyn na jeho realizaci).

 

Chtěl bych z aplikace napsané v C# zavolat API funkci Windows, ovšem nemůžu najít způsob, jak to provést. Mohl byste mi poradit?

 

Budete-li se pokoušet volat z řízené aplikace neřízenou funkci aplikačního programovacího rozhraní (API) Windows, budete muset splnit před samotným použitím vámi požadované funkce několik podmínek:

 

1.      Deklaraci API funkce musí předcházet použití speciálního atributu DllImportAttribute, který se nachází ve jmenném prostoru System.Runtime.InteropServices. Atribut DllImportAttribute naznačuje, že bude importována API funkce s neřízeným kódem. Ve skutečnosti je atribut pouze jiným označením pro speciální třídu se stejným názvem (DllImportAttribute). Konstruktor této třídy pracuje s jedním parametrem typu string, kterým je název DLL knihovny, ve které je uložen kód API funkce, kterou chcete použít.

 

2.      Deklarační příkaz API funkce musí obsahovat klíčová slova static a extern.

 

3.      Deklarace API funkce v řízeném kódu musí přesně odpovídat prototypu funkce v neřízeném kódu. To znamená, že deklarace musí obsahovat stejné jméno, dále stejnou signaturu s příslušnými datovými typy formálních parametrů funkce a také stejnou návratovou hodnotu (pokud existuje).

 

Jestliže budou splněny tyto podmínky, bude API funkce úspěšně naimportována do kódu vaší řízené aplikace. S API funkcí můžete posléze pracovat jako s jakoukoliv jinou funkcí. Podívejme se na ukázku použití API funkce operačního systému Windows:

 

             // ... Tento programový kód se nachází uvnitř třídy Form1.

             static void Main()

             {

                    Application.Run(new Form1());

             }

 

             [System.Runtime.InteropServices.DllImport("User32")]

                    private static extern bool MoveWindow(int hWnd,

                    int X, int Y, int nWidth, int nHeight, bool bRepaint);

 

             [System.Runtime.InteropServices.DllImport("User32")]

                    private static extern bool AnimateWindow(int hWnd,

                    uint dwTime, uint dwFlags);

 

             private void button1_Click(object sender, System.EventArgs e)

             {

                    frm = new Form();

                    frm.Load += new EventHandler(frm_Load);

                    frm.Show();

             }

            

             Form frm;

 

             private void frm_Load(object sender, System.EventArgs e)

             {

                    int handle = frm.Handle.ToInt32();

                    MoveWindow(handle, 362, 234, 300, 300, true);

                    AnimateWindow(handle, 200, 0x00080000);

             }

             // ... zde pokračuje kód třídy Form1.

 

Ukázkový programový kód používá pro přesun a animaci nově vytvořené instance třídy Form dvě API funkce: MoveWindow a AnimateWindow. Před každou z funkcí se nachází atribut DllImport se jménem dynamicky linkované knihovny (DLL), ve které je uložen kód dané funkce (obě zde použité funkce jsou uloženy v souboru User32.dll). Velmi důležité je, aby byl atribut zapsán v hranatých závorkách. Za atributem se nachází samotný deklarační příkaz, který je složen z modifikátoru přístupu (private), klíčových slov static a extern, typem návratové hodnoty funkce a její signaturou (datové typy přitom musí odpovídat datovým typům použitím v neřízeném kódu API funkce). Ve výše uvedené programové ukázce je dále deklarována referenční proměnná frm s oborem třídy, které může uchovávat referenci na instance třídy Form.  V událostní proceduře button1_Click je vytvořena nová instance třídy Form, přičemž dochází také k vytvoření systémového delegáta (EventHandler) pro zpracovatele události Load nově vytvořené instance. Jakmile bude aktivována metoda Show objektu frm, delegát usměrní program tak, aby byl uskutečněn programový kód, jenž se nachází ve zpracovateli události frm_Load. Když dojde k události Load objektu frm, bude vykonán kód zpracovatele frm_Load (k události dojde po zavolání metody Show příslušné instance). Zpracovatel události frm_Load obsahuje následující řádky programového kódu:

 

private void frm_Load(object sender, System.EventArgs e)

       {

             int handle = frm.Handle.ToInt32();

             MoveWindow(handle, 362, 234, 300, 300, true);

             AnimateWindow(handle, 200, 0x00080000);

       }

 

Co ve skutečnosti tento kód provádí? Tak především, je získán handle okna instance třídy Form. Handle představuje jednoznačný identifikátor, pomocí kterého Windows přistupují k vytvořené instanci (hodnota identifikátoru handle je v tomto případě převedena do podoby 32bitového celého čísla pomocí metody ToInt32). Handle okna potřebujeme získat, neboť jej využijeme v obou API funkcích. Abyste přesně věděli, co uvedené API funkce provádějí, představíme si je podrobněji:

 

1.      API funkce MoveWindow má tuto všeobecní podobu (v C/C++):

 

BOOL MoveWindow(

  HWND hWnd,      // handle okna

  int X,          // horizontální pozice okna

  int Y,          // vertikální pozice okna

  int nWidth,     // šířka okna

  int nHeight,    // výška okna

  BOOL bRepaint   // volba, která určuje, zdali bude po přemístění realizováno                                   

                  // také překreslení okna

);

 

Podoba naší funkce je následovní:

 

MoveWindow(handle, 362, 234, 300, 300, true);

 

Funkce zachovává implicitní velikost okna instance třídy Form (300x300 pixelů), ovšem mění pozici levého horního bodu okna na bod se souřadnicemi (362, 234). Poslední parametr (bRepaint) je nastaven na hodnotu true, což znamená, že ihned poté, co se okno instance přesune, bude provedeno jeho překreslení.

    

2.      API funkce AnimateWindow má tuto všeobecní podobu (opět v C/C++):

 

BOOL AnimateWindow(

  HWND hwnd,     // handle okna

  DWORD dwTime,  // doba trvání animace okna

  DWORD dwFlags  // typ animace

);

 

A opět se podívejme na naší implementaci funkce AnimateWindow:

 

AnimateWindow(handle, 200, 0x00080000);

 

Prvním parametrem funkce je handle okna, které bude animováno. Druhý parametr (dwTime) je datového typu DWORD (typ DWORD představuje 32bitové celé číslo bez znaménka, jehož maximální hodnota je 232-1). V C# se typ DWORD samozřejmě nepoužívá, ovšem jeho ekvivalentem je datový typ uint, jenž disponuje stejným rozsahem platných celočíselných hodnot. Parametr dwTime odpovídá době trvání animace, která se udává v milisekundách. V našem případě bude animace trvat 200 milisekund, což je doporučená hodnota daná způsobem práce operačního systému. Nejhrozivěji pravděpodobně vypadá třetí parametr (dwFlags), jehož hodnotou je podivně vyhlížející číslo v šestnáctkové soustavě 0x0008000. Numerická hodnota 0x0008000 odpovídá interní konstantě AW_BLEND, která se používá v případě, kdy má být okno animováno pomocí tzv. fade efektu. Fade efekt znamená plynulé animování zobrazení okna, přičemž na začátku je okno takřka průhledné a postupně se „zaplňuje“ do standardního barevného schématu a podoby.  

 

Tip

Pokud přemýšlíte nad tím, jak vyjádřit číslo 0x0008000 v desítkové soustavě, zde je návod:

 

1.      Z numerické hodnoty 0x0008000 nejprve odstraníme prefix 0x, který vizuálně připomíná, že pracujeme s číslem v šestnáctkové soustavě. Tím pádem získáváme hodnotu 0008000.

 

2.      Hodnotu 0008000 přetypujeme na nám srozumitelné číslo podle tohoto vzorce:

 

0008000 = 0*167+0*166+0*165+8*164+0*163+0*162+0*161+0*160

 

3.      Z celého vzorce nakonec získáme pouze jedinou hodnotu, kterou je číslo 524 288 (8*164=524 288).

 

Doposud jsem programoval ve Visual Basicu .NET. Myslíte, že bych se mohl naučit programovat také ve Visual C#?

 

Zcela jistě ano. Visual C# je velmi zajímavý jazyk, který vhodně kombinuje rychlý návrh aplikací, jenž je známý právě z Visual Basicu a flexibilní syntaxi, která je předností jazyka C++. Protože všechny jazyky platformy .NET využívají společné integrované prostředí, na první pohled možná ani nepostřehnete, že se nacházíte uvnitř Visual C#. Visual C# taktéž nabízí vizuální vývoj aplikací, takže grafickou podobu aplikace můžete navrhnout přesně tak, jak to děláte ve Visual Basicu .NET. Na druhé straně je pravdou, že zápis programového kódu se od Visual Basicu podstatně liší a zde vám zcela určitě pomůže předcházející zkušenost s jazykem C nebo C++ (případně s Javou). Pokud neznáte uvedené jazyky, nemusíte zoufat. Seriál Začínáme s jazykem Visual C# vám poskytne všechny potřebné informace nato, abyste mohli psát své první programy i ve Visual C#. Ve skutečnosti jsou jazyky Visual C# a Visual Basic .NET do velké míry podobné ve smyslu implementace stejných, nebo přinejmenším velmi podobných programovacích prvků. Ku příkladu, oba jazyky umožňují řídit tok programu pomocí rozhodovací konstrukce If (if v C#), ovšem zápis této konstrukce je v C# jiný než ve Visual Basicu (podobně je tomu také třeba při cyklech či prvcích OOP). Sečteno a podtrženo, jestliže dobře ovládáte Visual Basic .NET, při pilném studiu se do tajů Visual C# můžete dostat velmi rychle. Poněkud problematičtější je ovšem přechod k Visual C# pro programátory, kteří pracují s Visual Basicem verze 6. Ty totiž budou muset nejdříve zvládnout všechny inovace a modifikace, jež přináší vývojová platforma .NET.            

 

Pracuji s formulářem, který bych potřeboval vertikálně i horizontálně vycentrovat na obrazovce počítače. Vím, že v režimu návrhu mohu nastavit vlastnost StartPosition na hodnotu CenterScreen, co mám však udělat, když chci provést vystředění formuláře za běhu programu?

 

Událostní proceduru Load formuláře upravte do níže uvedené podoby:

 

       private void Form1_Load(object sender, System.EventArgs e)

             {

                    this.Top = (Screen.PrimaryScreen.Bounds.Height - this.Height) / 2;

                    this.Left = (Screen.PrimaryScreen.Bounds.Width - this.Width) / 2;

             }

 

Abyste mohli provést vystředění formuláře, musíte znát rozměry zobrazovací plochy obrazovky a rozměry samotného formuláře. Rozměry formuláře jsou známé, nakolik Visual C# implicitně vytváří formuláře s velikostí 300x300 obrazových bodů neboli pixelů. Při zrození instance formuláře jsou do vlastností Height a Width této instance uloženy hodnoty 300 pixelů. Pro zjištění rozměrů viditelné plochy obrazovky použijeme třídu Screen. Konstruktor třídy Screen ovšem není veřejný a proto není možné přímo vytvářet instance této třídy. Pokud ovšem zavoláme statickou vlastnost PrimaryScreen, podaří se nám získat instanci třídy Screen. Vrácená instance představuje hlavní zobrazovací jednotku počítačového systému (pokud systém pracuje jenom s jednou obrazovkou, bude vrácena tato, v opačném případě bude vrácena ta zobrazovací jednotka, která je nakonfigurována jako primární). Dále pokračujeme zavoláním vlastnosti Bounds vytvořené instance třídy Screen. Vlastnost Bounds vrací instanci třídy Rectangle (jde o objekt, jenž popisuje obdélníkový region hlavní zobrazovací jednotky). Máme-li instanci třídy Rectangle, můžeme konečně použít vlastnost Height (resp. Width) pro zjištění výšky (resp. šířky) viditelné plochy obrazovky. Předpokládejme, že vlastníte 17-palcový monitor a používáte rozlišení 1024x768 obrazových bodů. Za těchto podmínek vrátí příkaz Screen.PrimaryScreen.Bounds.Height hodnotu 768 a příkaz Screen.PrimaryScreen.Bounds.Width zase vrátí hodnotu 1024. Od takto získaných hodnot posléze odečteme hodnoty, které reprezentují výšku (resp. šířku) formuláře a finální výsledky vydělíme dvěma. Do vlastnosti Top formuláře bude po provedení všech nastíněných operací uložena hodnota 234, zatímco vlastnost Left bude obsahovat hodnotu 362 (viz obrázek).

 

     

 

Chtěl bych se zeptat, jak je možné ve Visual C# vytvářet zpracovatele události, a to jak v režimu návrhu, tak i za běhu aplikace. 

 

V režimu návrhu aplikace vytvoříte zpracovatele události takto:

 

1.      Na formuláři vyberte ten ovládací prvek, pro který chcete vytvořit zpracovatele události.

2.      V okně Properties Window klepněte na tlačítko Events ().

3.      Vyhledejte položku s názvem požadované události (např. Click) a poklepejte na tuto položku.

4.      Visual C# okamžitě vygeneruje programovou kostru událostní procedury a otevře okno editoru pro zápis programového kódu.

5.      Vyplňte tělo vytvořené událostní procedury vhodným programovým kódem.

 

Jestliže budete chtít spojit událost s jejím zpracovatelem za běhu programu, budete muset všechny potřebné operace provést přímo z programového kódu. Aby bylo možné vytvořit spojení mezi událostí a příslušným zpracovatelem, musíte vytvořit systémového delegáta, který bude zodpovědný za to, aby událost našla svého zpracovatele.

 

Následující fragment kódu byl vyňat ze třídy Form1. Můžete si v něm všimnout událostní proceduru button1_Click. V této proceduře dochází k vytvoření nové instance třídy Button (s názvem btnTlačítko1), dále jsou nastaveny některé klíčové vlastnosti nově vytvořené instance a instance je přidána do kolekce ovládacích prvků formuláře.

 

Spojení mezi události Click tlačítka btnTlačítko1 a zpracovatelem této události (btnTlačítko1_Click) zabezpečuje systémový delegát EventHandler. Při této programové operaci je velmi důležité správné použití operátorů += a new. Nakonec už stačí jenom napsat událostní proceduru btnTlačítko1_Click, která ovšem musí disponovat stejnými prvky, jako systémový delegát (nesmí vracet hodnotu a musí pracovat se dvěma parametry typu object a System.EventArgs). Pokud máte zkušenosti s jazykem C nebo C++, můžete si delegáta představit jako funkční ukazatel, který je ale v jazyce Visual C# zcela typově bezpečný. Delegát tak ve skutečnosti obsahuje runtime adresu funkce, v našem případě zpracovatele události, a tohoto zpracovatele aktivuje vždy, když je o to požádán (neboli vždy, když uživatel klepne na tlačítko).   

 

             private void button1_Click(object sender, System.EventArgs e)

             {

                    Button btnTlačítko1 = new Button();

                    btnTlačítko1.Text = "Tlačítko";

                    btnTlačítko1.Location = new Point(10, 100);

                    btnTlačítko1.Size = new Size(200, 100);

                    this.Controls.Add(btnTlačítko1);

                    btnTlačítko1.Click += new System.EventHandler(btnTlačítko1_Click);

             }

 

             private void btnTlačítko1_Click(object sender, EventArgs e)

             {

                    MessageBox.Show("Právě jste klepli na tlačítko.");

             }

 

Graficky můžeme vztah mezi událostí, jejím zpracovatelem a systémovým delegátem znázornit takto:

 

 

 

Ján Hanák