Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop

A Simple WPF Text Clock Gadget for Windows

4.73/5 (20 votes)
28 Jan 2013CPOL2 min read 44K   1.9K  
A simple WPF text clock gadget for Windows

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:

C#
string TextTime = String.Empty; // for holding the text-time
static Dictionary<int, string> Mapping; // maps numbers with the corresponding text;
// e.g. 9 -> "NINE"
C#
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.

C#
static string CHAR_SEQUENCE = 
  "ITRISRAOTENTWENTYPHALFQUARTERFIVEMINUTESXTOBPASTHONETWOTHREEELEVENFOURFIVESIXSEVENQEIGHTNINE
   JTWELVETENPCOCLOCK";

Below is the XAML markup of the grid that I am using to add TextBlocks 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 TextBlocks in the XAML grid each containing a character as its Text property.

ASP.NET
<Grid HorizontalAlignment="Left" Margin="0,0,0,0" 
          Name="grdChars" VerticalAlignment="Top">
    <Grid.BitmapEffect>
        <DropShadowBitmapEffect></DropShadowBitmapEffect>
    </Grid.BitmapEffect>
</Grid>
C#
private static void FillCharGrid() // for converting CHAR_SEQUENCE into a 2D array
{
    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() // Render chars in 2D as separate TextBlocks
{
    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.

C#
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 strings 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":

C#
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.

C#
public MainWindow()
{
    InitializeComponent();
    InitializeComponent();
    RenderCharGrid();
    // return;
    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.

XML
<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>
C#
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    this.DragMove();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    this.Close();
}

Enjoy...!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)