Vlastní ovládací prvky pro aplikace
autor Lukáš Kouřil
| publikováno 3. května 2008
Spolu s Visual Studiem je dodávána spousta ovládacích prvků, jako jsou Button, Listbox, Label a další, které je možné použít pro vytváření GUI aplikace. Co však dělat, pokud si ze spousty možností, které jsou ve Visual Studiu standardně, nevyberete, protože ani jeden z nich neposkytuje funkčnost, specifickou pro Vaši aplikaci? V podstatě máte dvě možnosti - zkusit najít na internetu Vám vyhovující komponentu, anebo si vytvořit vlastní ovládací prvek. V tomto článku se dozvíte, jak na druhou možnost.
Úplně na začátku je nutné se zamyslet, zdali je možné komponentu, kterou chceme vytvořit, poskládat z jiných, již hotových prvků, nebo jestli bude náš ovládací prvek natolik specifický, že si jej budeme muset celý nakreslit od základu sami. V obou případech není programování komponenty vůbec složité, jen druhý případ zabere o trochu víc času.
V tomto článku se podíváme na oba typy ovládacích prvků a vytvoříme si vlastní plnohodnotné komponenty. Půjde o prvky představující minutku, u níž si nastavíme čas, který bude odpočítáván, a jehož zbývající hodnota bude vizuálně reprezentována.
1. Skládané (Composite) User Controls
Jak je patrné z názvu, tyto uživatelské prvky se vytvářejí skládáním z již hotových komponent. Náš prvek tedy budeme skládat z komponent:
-
Timer zajišťující měření času
-
ProgressBar, jenž bude zajiš'tovat vizuální prezentaci času
-
Label pro zobrazení odpočítavaného času v textové podobě
Dále budeme chtít, aby komponenta implementovala metodu Start, která spustí odpočet času, a vlastnosti nastavované v Properties Window:
-
TimeLimit, pro nastavení odpočítávaného času
-
AlertMessage, nastavující hlášení zobrazené po vypršení času
-
CountDownVisibility, viditelnost odpočítávaného času v textové podobě
-
Reversion, říkající, zdali chceme zobrazovat odpočítávaný čas přidáváním, nebo odebíráním dílků v ProgressBaru
Nyní již máme hotový návrh ovládacího prvku a můžeme spustit Visual Studio. Zvolíme File -> New -> Project. Ve Visual Studiu 2008 zvolíme šablony pro .NET Framework 2.0, jazyk Visual C#, šablonu Windows Form Control Library, projekt pojmenujeme jako MyControls a necháme vytvořit.
Když se nyní podíváte na zdrojový kód vygenerované třídy zjistíte, že jde o úplně obyčejnou třídu, která je odvozena od třídy UserControl. Jako první co uděláme je, že si nově vytvářenou komponentu vhodně pojmenujeme. Zobrazíme si design komponenty a v Properties Window nastavíme vlastnost Name na SkladanaMinutka.
V tuto chvíli pokračujme přesně podle návrhu. Na plochu komponenty umístěme Timer, ProgressBar a Label. ProgressBar přesuňte do levého horního rohu, Label přibližně do středu ProgressBaru a upravte velikost plochy celého ovládacího prvku tak, aby byla stejná jako velikost ProgressBaru. Navíc nastavte vlastnost Visibile prvku Label na false.
Začněme tím, že si vytvoříme vnitřní proměnné, které budou zajišťovat uchovávání hodnot:
// interval pro Timer - 1s
private int _interval = 1000;
// odpočítávaný čas
private int _limit = 0;
// hlášení zobrazené po vypršení času
private string _alertMessage = "";
// přibývání nebo ubývání ProgressBaru
private bool _reversion = false;
// aktuální čas
private int _current = 0;
// viditelnost textového odpočtu času
private bool _countDownVisibility = false;
Teď si upravme konstruktor prvku, ve kterém bude nastaven interval komponenty Timer:
public SkladanaMinutka()
{
InitializeComponent();
timer1.Interval = _interval;
}
Protože budeme chtít, aby se textové zobrazení odpočítávaného času zobrazovalo přesně uprostřed ProgressBaru, napíšeme si krátkou funkci, která bude zajišťovat přepočet pozice prvku Label.
private void Align()
{
label1.Left = (this.Width - label1.Width) / 2;
label1.Top = (this.Height - label1.Height) / 2;
}
Nyní musíme zajistit, aby při změně velikosti celého ovládacího prvku SkladanaMinutka došlo také ke změně ProgressBaru tak, aby byl vždy přes celou plochu komponenty.
V Properties Window se přepněte na zobrazení událostí (Events) a vygenerujte event handler pro událost Resize našeho ovládacího prvku SkladanaMinutka. V tomto handleru bude umístěn kód který zajistí změnu velikosti a zároveň vycentrování prvku Label.
private void SkladanaMinutka_Resize(object sender, EventArgs e)
{
progressBar1.Size = this.Size;
Align();
}
Dosud jsme programovali pouze vnitřní chování komponenty, nyní vytvoříme části, které budou viditelné pro uživatele našeho prvku a pomocí nichž bude možné s ovládacím prvkem pracovat. Nejprve to budou vlastnosti TimeLimit, AlertMessage, CoutDownVisibility a Reversion, které budou zobrazeny v Properties Window.
[Category("Behavior")]
[Description("Nastaví časový limit")]
[DisplayName("TimeLimit")]
public int Limit
{
get
{
return _limit;
}
set
{
_limit = value;
}
}
[Category("Behavior")]
[Description("Nastaví hlášení při vypršení limitu")]
[DisplayName("AlertMessage")]
public string Alert
{
get
{
return _alertMessage;
}
set
{
_alertMessage = value;
}
}
[Category("Behavior")]
[Description("Nastaví způsob zobrazení odpočtu")]
[DisplayName("Reversion")]
public bool Reversion
{
get
{
return _reversion;
}
set
{
_reversion = value;
}
}
[Category("Behavior")]
[Description("Zobrazit textové počítadlo")]
[DisplayName("CountDownVisibility")]
public bool Pocitadlo
{
get
{
return _countDownVisibility;
}
set
{
_countDownVisibility = value;
}
}
Jak vidíte, jde o zcela běžné vlastnosti. To, co stojí za pozornost, jsou atributy těchto vlastností. Mezi atributy, které jsme použili patří:
-
Category, která přiřazuje vlastnost do určité skupiny (v případě, že máte v Properties Window zapnuto zobrazení vlastností ve skupinách)
-
Description, což je popisek vlastnosti, zobrazovaný ve spodní části Properties Window
-
DisplayName, pojmenovávající danou vlastnost. Pokud tento atribut není použit, zobrazovaný název vlastnosti je převzat z názvu vlastnosti uvedeného v kódu
Pro spuštění odpočtu budeme potřebovat veřejnou metodu, který může vypadat takto:
public void Start()
{
// vynulování aktuálního času
_current = 0;
// stanovení aktuální hodnoty ProgressBaru, minima a maxima
progressBar1.Value = _reversion ? _limit : 0;
progressBar1.Minimum = 0;
progressBar1.Maximum = _limit;
// nastavení textového ukazatele zbývajícího času
label1.Visible = _countDownVisibility;
label1.Text = string.Format("{0} s.", _limit - _current);
Align();
// spuštění Timeru
timer1.Start();
}
Úplně poslední, co musíme udělat, je implentovat samotný pohyb ProgressBaru, což provedeme v event handleru Tick komponenty Timer. V design módu se v Properties Window přepneme na zobrazení událostí (Events) a poklepáním vygenerujeme handler pro událost Tick.
private void timer1_Tick(object sender, EventArgs e)
{
// byl dosažen konec odpočtu?
if (_current != _limit)
{
// směr pohybu ProgressBaru
progressBar1.Value = _reversion ? --progressBar1.Value : ++progressBar1.Value;
// aktuální čas
_current++;
// textový ukazatel zbývajícího času
label1.Text = string.Format("{0} s.", _limit - _current);
Align();
}
else
{
timer1.Stop();
label1.Text = "Nastavený čas vypršel!";
Align();
label1.Visible = true;
// jestliže bylo zadáno hlášení, zobrazí jej
if (!string.IsNullOrEmpty(_alertMessage))
MessageBox.Show(_alertMessage, "Minutka",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
Tím je celá komponenta hotová. Vylepšit ji však můžeme přidáním ikony, kterou bude prvek reprezentován v ToolBoxu ve Visual Studiu. To uděláme přidáním atributu ToolboxBitmap před deklaraci třídy:
namespace MyControls
{
[ToolboxBitmap(typeof(ProgressBar))]
public partial class SkladanaMinutka : UserControl
{
// interval pro Timer - 1s
private int _interval = 1000;
// odpočítávaný čas
private int _limit = 0;
Jako parametr atributu je možné uvést cestu k obrázku (který však musí být velikosti 16x16) nebo typ, ze kterého bude ikona převzata.
Komponentu můžete nyní otestovat buď přidáním nového projektu do aktuálního Solution, nebo vytvořením nového Solution. V tom případě bude nutné přidat komponentu do projektu manuálně, kliknutím pravým tlačítkem myši v Toolboxu a zvolením Chose items... -> Browse a vybráním knihovny s komponentou.
2. Custom Controls
Custom Controls jsou na rozdíl od kompozitních - skládaných o trochu složitější. Je to z toho důvodu, že musíme u těchto prvků řídit samotné vykreslování. To nám zároveň dává velkou možnosti, vytvořit si téměř libovolnou komponentu.
Pro naše demonstrační účely si opět vytvoříme ovládací prvek, který bude reprezentovat minutku pro odpočet času. Grafická podoba zbývajícího času bude zobrazena jako přibývající nebo ubývající "koláč". Struktura celého prvku bude po stránce zdrojového kódu stejná jako u komponenty SkladanaMinutka. Pouze nebudeme potřeboval některé části.
V Solution Exporeru klikněte pravým tlačítkem a zvolte Add -> New Item -> Custom Control -> Add. Tím dojde k přídání nové třídy, odvozené od třídy Control. Uvnitř je již vygenerován event handler pro událost Paint. Pojmenujme si nový prvek tím, že se přepneme do pohledu Design a v Properties Window nastavíme vlastnost Name na NovaMinutka.
Protože bude tato komponenta opět využívat Timer, přidejme jej tedy na plochu ovládacího prvku.
Jako minule, začněme vytvořením vnitřních proměnných:
// interval pro Timer - 1s
private int _interval = 1000;
// odpočítávaný čas
private int _limit = 1;
// hlášení zobrazované po vypršení času
private string _alertMessage = "";
// přibývání nebo ubývání "koláče"
private bool _reversion = false;
// aktuální čas
private int _current = 0;
// úhel vykrojení "koláče"
private int _angle = 0;
V konstruktoru nastavíme interval pro Timer:
public NovaMinutka()
{
InitializeComponent();
timer1.Interval = _interval;
}
Nyní potřebujeme vlastnosti, pro nastavování prvku:
[Category("Behavior")]
[Description("Nastaví časový limit")]
[DisplayName("TimeLimit")]
public int Limit
{
get
{
return _limit;
}
set
{
// musí být větší než nula z důvodu vykreslování prvku
// viz. event handler OnPaint
if (value == 0)
{
MessageBox.Show("Musí být větší než nula");
}
else
{
_limit = value;
}
}
}
[Category("Behavior")]
[Description("Nastaví hlášení pri vypršení limitu")]
[DisplayName("AlertMessage")]
public string Alert
{
get
{
return _alertMessage;
}
set
{
_alertMessage = value;
}
}
[Category("Behavior")]
[Description("Nastaví způsob zobrazení odpočtu")]
[DisplayName("Reversion")]
public bool Reversion
{
get
{
return _reversion;
}
set
{
_reversion = value;
}
}
V tuto chvíli následuje veřejná metoda pro spuštění minutky:
public void Start()
{
// vynulování aktuálního času
_current = 0;
// vynulování vykrojení "koláče"
_angle = 0;
// vynucení překreslení prvku
Refresh();
// spuštění Timeru
timer1.Start();
}
V pohledu Design se v Properties Window přepneme do zobrazení událostí a dvojklikem necháme vytvořit event handler Tick pro prvek Timer.
Do tohoto handleru vložíme:
private void timer1_Tick(object sender, EventArgs e)
{
// byl dosažen konec odpočtu?
if (_current != _limit)
{
// aktuální čas
_current++;
// vynucení překreslení
this.Refresh();
}
else
{
timer1.Stop();
// jestliže bylo nastavení hlášení, zobrazí jej
if (!string.IsNullOrEmpty(_alertMessage))
MessageBox.Show(_alertMessage, "Minutka",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
Na závěr potřebujeme zajistit vykreslení prvku. K tomu použijeme již vygenerovaný handler OnPaint, který rozšíříme:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
_angle = _reversion ? (360 / _limit) * (_limit - _current) : (360 / _limit) * _current;
pe.Graphics.FillPie(new SolidBrush(Color.Salmon),
new Rectangle(2, 2, this.Width - 5, this.Height - 5),
-90, _angle);
pe.Graphics.DrawEllipse(new Pen(new SolidBrush(Color.Salmon)),
new Rectangle(0, 0, this.Width - 1, this.Height - 1));
}
Tím máme celou komponentou hotovou. Opět můžeme ještě nastavit ikonu komponenty:
namespace MyControls
{
[ToolboxBitmap(typeof(Timer))]
public partial class NovaMinutka : Control
{
// interval pro Timer - 1s
private int _interval = 1000;
// odpočítávaný čas
private int _limit = 1;
A to je už opravdu vše. Jak vidíte vytváření vlastních ovládacích prvků není vůbec nic těžkého a zvládne jej každý, znající základy .NET Frameworku.