Introduction
This is a simple text-based clock gadget with Windows Presentation Foundation (WPF) where the time is displayed as a set of highlighted characters on a static character grid.
Background
Many of you must have seen many variants of text-based clock. So I thought it would be great if I have this in the form of a desktop gadget. Since I am just a beginner with WPF (and C# as well), any suggestion in any form is most welcome.
Using the Code
The functions that are responsible for generating the text-based current time go like below:
string TextTime = String.Empty;
static Dictionary<int, string> Mapping;
private string GetTextTime()
{
TextTime = "IT IS ";
Now = DateTime.Now;
int sec = Now.Second;
int min = Now.Minute;
int hour = Now.Hour;
if (min < 5)
TextTime += GetStringValue(hour) + " OCLOCK";
else if (min < 10)
TextTime += "FIVE MINUTES PAST " + GetStringValue(hour);
else if (min < 15)
TextTime += "TEN MINUTES PAST " + GetStringValue(hour);
else if (min < 20)
TextTime += "A QUARTER PAST " + GetStringValue(hour);
else if (min < 25)
TextTime += "TWENTY MINUTES PAST " + GetStringValue(hour);
else if (min < 30)
TextTime += "TWENTY FIVE MINUTES PAST " + GetStringValue(hour);
else if (min < 35)
TextTime += "A HALF PAST " + GetStringValue(hour);
else if (min < 40)
TextTime += "TWENTY FIVE MINUTES TO " + GetStringValue(hour + 1);
else if (min < 45)
TextTime += "TWENTY MINUTES TO " + GetStringValue(hour + 1);
else if (min < 50)
TextTime += "A QUARTER TO " + GetStringValue(hour + 1);
else if (min < 55)
TextTime += "TEN MINUTES TO " + GetStringValue(hour + 1);
else
TextTime += "FIVE MINUTES TO " + GetStringValue(hour + 1);
return TextTime;
}
private static string GetStringValue(int n)
{
n = n % 12;
string value = (Mapping.Where(m => m.Key == n)).ToList()[0].Value;
return value;
}
Then, I have a char
sequence that is used to generate the character-grid.
static string CHAR_SEQUENCE =
"ITRISRAOTENTWENTYPHALFQUARTERFIVEMINUTESXTOBPASTHONETWOTHREEELEVENFOURFIVESIXSEVENQEIGHTNINE
JTWELVETENPCOCLOCK";
Below is the XAML markup of the grid that I am using to add TextBlock
s containing characters in. I am using a couple of functions respectively for converting the CHAR_SEQUENCE
string into a 2D array of characters and adding TextBlock
s in the XAML grid each containing a character as its Text
property.
<Grid HorizontalAlignment="Left" Margin="0,0,0,0"
Name="grdChars" VerticalAlignment="Top">
<Grid.BitmapEffect>
<DropShadowBitmapEffect></DropShadowBitmapEffect>
</Grid.BitmapEffect>
</Grid>
private static void FillCharGrid()
{
var charArr = CHAR_SEQUENCE.ToCharArray();
int j = 0;
for (int i = 0; i < charArr.Length; i++, j++)
{
char c = charArr[i];
CharGrid[i / 11, j % 11] = c;
}
}
private void RenderCharGrid()
{
FillCharGrid();
int topMargin = 20;
for (int i = 0; i < CharGrid.GetLength(0); i++)
{
int leftMargin = 0;
for (int j = 0; j < CharGrid.GetLength(1); j++)
{
TextBlock txt = new TextBlock();
txt.Name = "lbl" + i.ToString() + j.ToString();
txt.Text = CharGrid[i, j].ToString();
txt.FontFamily = new System.Windows.Media.FontFamily("Tahoma");
txt.Height = 30;
txt.HorizontalAlignment = HorizontalAlignment.Left;
txt.TextAlignment = TextAlignment.Center;
txt.Margin = new Thickness(leftMargin, topMargin, 0, 0);
txt.VerticalAlignment = VerticalAlignment.Top;
txt.Width = 35;
txt.Foreground = new SolidColorBrush(Colors.DarkGray);
txt.Opacity = 2;
leftMargin += 20;
grdChars.Children.Add(txt);
}
topMargin += 20;
}
}
Following is the method that I am using to highlight the characters that are being part of the current text-time generated by GetTextTime()
method.
private void HighlightTextTime(string time)
{
UIElementCollection elements = grdChars.Children;
var timeArr = time.Split(new char[] { ' ' });
bool toBreak = false;
bool isFlattenedWRTFive = IsFlattenedWRTFive(time);
bool isFlattenedWRTTen = IsFlattenedWRTTen(time);
int currentFive = 0; int currentTen = 0;
foreach (var str in timeArr)
{
var arr = str.ToCharArray();
List<textblock> labels = new List<textblock>();
for (int i = 0; i < grdChars.Children.Count; i++)
{
if (toBreak)
{
toBreak = false;
break;
}
TextBlock txt = elements[i] as TextBlock;
if (txt.Opacity == 2)
continue;
bool flag = false;
List<textblock> Reds = new List<textblock>();
if (txt.Text.ToUpper().Equals(str[0].ToString().ToUpper()))
{
for (int j = 0; j < str.Length; j++)
{
TextBlock txt_succ = elements[i + j] as TextBlock;
if (!txt_succ.Text.ToUpper().Equals(str[j].ToString().ToUpper()))
{
flag = false;
Reds.Clear();
break;
}
else
{
flag = true;
Reds.Add(txt_succ);
}
}
if (flag)
{
if (str.ToUpper().Equals("FIVE") && !isFlattenedWRTFive && currentFive == 0)
{
currentFive = 1;
continue;
}
if (str.ToUpper().Equals("TEN") && !isFlattenedWRTTen && currentTen == 0)
{
currentTen = 1;
continue;
}
for (int p = 0; p < Reds.Count; p++)
{
Reds[p].Foreground = new SolidColorBrush(Colors.Red);
Reds[p].Opacity = 2;
}
toBreak = true;
}
}
}
}
}
I had slightly been caught with times like IT IS FIVE MINUTES TO FIVE
or IT IS TEN MINUTES TO TWELVE
, since there are two char sets for "FIVE" and "TEN". First for minutes and later is for hours. So, I wrote a simple method to determine whether the current text-time is flattened with respect to the string
s FIVE
or TEN
since they can represent minutes or hour, and if it's for hours, I simply skipped the first char
set for that string
and used the later one. I know this is not at all a good way to handle this, but it worked quite well. Here is a sample method that checks if the text-time is flattened wrt string
"TEN
":
private bool IsFlattenedWRTTen(string time)
{
time = time.ToUpper();
var tenCount = CountSubStrings(time, "TEN");
if ((tenCount == 2 || tenCount == 0))
return true;
if (time.Contains("MINUTES"))
if (time.IndexOf("TEN") < time.IndexOf("MINUTES"))
return true;
return false;
}
Now, this is, as you may point out, quit heavy processing since it is to be executed after every second. So, I used a string
CURRENT_TEXT_TIME
that stores the current text-time. After each second, when new text-time is generated, it's first compared to this string
, and if they are different, the highlighted characters are flushed and new text-time is highlighted.
So here is the constructor and timer_Tick()
event handler.
public MainWindow()
{
InitializeComponent();
InitializeComponent();
RenderCharGrid();
Mapping = new Dictionary<int,>();
FillMapping();
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
string time = GetTextTime();
if (CURRENT_TEXT_TIME.ToUpper().Equals(time.ToUpper()))
return;
CURRENT_TEXT_TIME = time;
ResetCharGrid();
HighlightTextTime(time);
}
And last but not the least, since we want to create a desktop gadget style application, we have to set a few properties of the XAML window, a button to close the gadget and finally a Window_MouseLeftButtonDown()
handler to enable dragging the gadget. Below is the XAML code for window, the Close button Click
event handler and mouse click handler.
<Window x:Class="TextClock.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextClock by 007" Height="248" Width="253"
AllowsTransparency="True"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
Opacity="0.8"
Background="Black"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
ShowInTaskbar="False">
......
</window>
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
Enjoy...!!