Publikoval Michal Kočí dňa 10.5.2008 o 01:52 v kategórii Silverlight
V tomto príspevku si naprogramujeme analógové hodiny a na tomto príklade si okrem vytvárania inštancií ovládacích prvkov v kóde aj použitie časovača či niektoré možnosti práce s textom v Silverlight aplikáciach.
Analógové hodiny
Vytvoríme si analógové hodiny - kruh s čiarkami namiesto čísel a s textom namiesto čiar ručičiek. Hodiny budú vyzerať nasledovne:
XAML kód
Aby sme vytvorili takéto analógové hodiny, budeme potrebovať vykresliť:
Obrys ciferníka vykreslíme jednoducho, ako elipsu - inštanciu prvku Ellipse. Jednotlivé čiarky, teda zárezy pre každú z dvanástich hodín budeme vykreslovať v kóde. Ručičky, keďže sú len tri a každá je iná, budeme vykreslovať ako text - inštancie triedy TextBlock. Ručičky budeme animovať v kóde, ukážeme si tak použitie časovača.
Ako prvé vykresléme obrys, teda elipsu. Tým, že nastavíme rovnakú šírku ako výšku bude elipsa v tvare kruhu:
<Ellipse Width="296" Height="296" Stroke="Black" Fill="Azure"
Canvas.Left="2" Canvas.Top="2" />
Ďalej vykreslíme jednotlivé ručičky. Ich kód je veľmi podobný. Ručička je vlastne obyčajný TextBlock, s aplikovanými transformáciami - ScaleTransform ktorá ručičku natiahne, aby bola široká a siahala až skoro k obrysu ciferníka a RotateTransform ktorá bude ručiku animovať. RotateTransform si pomenujeme, aby sme s ňou mohli pracovať v kóde:
<TextBlock x:Name="HandHour" Text="HOUR" Foreground="CornflowerBlue"
Canvas.Left="150" Canvas.Top="150" Canvas.ZIndex="1"
FontFamily="Trebuchet MS" FontSize="16" >
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="3" ScaleY=".5"/>
<RotateTransform x:Name="HandHourRotate" Angle="0"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
Celý XAML kód potom vyzerá nasledovne:
<UserControl x:Class="ClockClient.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="300" Height="300">
<Canvas x:Name="LayoutRoot" Background="White">
<Ellipse Width="296" Height="296" Stroke="Black" Fill="Azure"
Canvas.Left="2" Canvas.Top="2" />
<TextBlock x:Name="HandHour" Text="HOUR" Foreground="CornflowerBlue"
Canvas.Left="150" Canvas.Top="150" Canvas.ZIndex="1"
FontFamily="Trebuchet MS" FontSize="16" >
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="3" ScaleY=".5"/>
<RotateTransform x:Name="HandHourRotate" Angle="0"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
<TextBlock x:Name="HandMinute" Text="MINUTE" Foreground="LightSteelBlue"
Canvas.Left="150" Canvas.Top="150" Canvas.ZIndex="2"
FontFamily="Trebuchet MS" FontSize="16" >
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="2.5" ScaleY=".5"/>
<RotateTransform x:Name="HandMinuteRotate" Angle="30"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
<TextBlock x:Name="HandSecond" Text="SECOND" Foreground="SkyBlue"
Canvas.Left="150" Canvas.Top="150" Canvas.ZIndex="3"
FontFamily="Trebuchet MS" FontSize="16" >
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="2.5" ScaleY=".5"/>
<RotateTransform x:Name="HandSecondRotate" Angle="60"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Canvas>
</UserControl>
Programový kód
Aby hodiny stále zobrazovali aktuálny čas, je potrebné minimálne každú sekundu aktualizaovať ich výzor, teda treba zabezpečiť, aby sa v pravidelných intervaloch ručičky nastavili do takej polohy, aby zobrazovali presný čas. Na toto použijeme časovač - objekt typu DispatcherTimer. Pripravíme si metódu, ktorá zinicializuje tento časovač, teda nastaví interval a metódu, ktorá bude v danom intervale volávaná:
private void InitializeTimer()
{
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 35);
timer.Tick += new EventHandler(Timer_Tick);
timer.Start();
}
V metóde Timer_Tick, ktorá bude volaná každých 35 milisekúnd potom vypočítame správny uhol, pod ktorým má byť ručička zobrazená a tento nastavý príslušným ručičkám, respektíve ich otočným transformáciám. Algoritmus je jednoduchý a to rozdeliť si ciferník, ktorý vlastne predstavuje 360° na rovnako veľké úseky predstavujúce hodiny alebo minúty, alebo sekundy (v závislosti od typu ručičky) a potom veľkosť tohto úseku prenásobiť aktuálnou hodnotou hodín alebo minút, alebo sekúnd:
void Timer_Tick(object sender, EventArgs e)
{
DateTime actualTime = DateTime.Now;
int hour = actualTime.Hour;
int minute = actualTime.Minute;
int second = actualTime.Second;
int millisecond = actualTime.Millisecond;
int totalMinutes = ((hour > 11 ? hour - 12 : hour) * 60) + minute;
int totalSecond = (minute * 60) + second;
int totalMillisecond = (second * 1000) + millisecond;
double hourAngle = (360 / 720.0) * totalMinutes;
hourAngle -= 90;
HandHourRotate.Angle = hourAngle;
double minuteAngle = (360 / 3600.0) * totalSecond;
minuteAngle -= 90;
HandMinuteRotate.Angle = minuteAngle;
double secondAngle = (360 / 60000.0) * totalMillisecond;
secondAngle -= 90;
HandSecondRotate.Angle = secondAngle;
}
No a ešte nám chýbajú značky pre jednotlivé hodiny. Tie budeme vykreslovať ako čiary (objekty typu Line) a budeme ich potrebovať 12 a mali by byť natočené vždy v násobkoch 30°. V cykle potom týchto čiar vytvoríme 12. Aby boli zobrazené, musíme ich pridať na Canvas a to metódou Add jeho vlastnosti Children. Ako však nastaviť pozíciu, teda dependency property Canvas.Left a Canvas.Top? Tieto sa nastavujú metódou SetValue ktorá ako parametre prijíma danú dependency property a hodnotu, ktorá sa má tejto vlastnosti nastaviť. Celý kód inicializačnej metódy potom vyzerá nasledovne:
private void CreateHourLines()
{
for (int i = 0; i < 12; i++)
{
Line line = new Line()
{
X1 = 0,
X2 = 0,
Y1 = 140,
Y2 = 147,
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 2,
RenderTransform = new RotateTransform() { Angle = i * 30 }
};
line.SetValue(Canvas.LeftProperty, 150);
line.SetValue(Canvas.TopProperty, 150);
LayoutRoot.Children.Add(line);
}
}
Celý kód potom vyzerá nasledovne:
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace ClockClient
{
public partial class Page : UserControl
{
DispatcherTimer timer;
public Page()
{
InitializeComponent();
InitializeTimer();
CreateHourLines();
}
private void CreateHourLines()
{
for (int i = 0; i < 12; i++)
{
Line line = new Line()
{
X1 = 0,
X2 = 0,
Y1 = 140,
Y2 = 147,
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 2,
RenderTransform = new RotateTransform() { Angle = i * 30 }
};
line.SetValue(Canvas.LeftProperty, 150);
line.SetValue(Canvas.TopProperty, 150);
LayoutRoot.Children.Add(line);
}
}
private void InitializeTimer()
{
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 35);
timer.Tick += new EventHandler(Timer_Tick);
timer.Start();
}
void Timer_Tick(object sender, EventArgs e)
{
DateTime actualTime = DateTime.Now;
int hour = actualTime.Hour;
int minute = actualTime.Minute;
int second = actualTime.Second;
int millisecond = actualTime.Millisecond;
int totalMinutes = ((hour > 11 ? hour - 12 : hour) * 60) + minute;
int totalSecond = (minute * 60) + second;
int totalMillisecond = (second * 1000) + millisecond;
double hourAngle = (360 / 720.0) * totalMinutes;
hourAngle -= 90;
HandHourRotate.Angle = hourAngle;
double minuteAngle = (360 / 3600.0) * totalSecond;
minuteAngle -= 90;
HandMinuteRotate.Angle = minuteAngle;
double secondAngle = (360 / 60000.0) * totalMillisecond;
secondAngle -= 90;
HandSecondRotate.Angle = secondAngle;
}
}
}
Záver
Veľmi jednoduchá aplikácia Vám teda poodhalila, ako používať časovač či ako dynamicky vytvárať inštancie prvkov. Miminum kódu a výsledok môže byť prívetivý. Ešte na aplikáciu poštvať dizajnéra a výsledok by bol ozaj pôsobivý...
Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.