Click here to Skip to main content
Click here to Skip to main content

A simple WPF Text Clock Gadget for Windows

, 28 Jan 2013
Rate this:
Please Sign up or sign in to vote.
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: 

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

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. 

<Grid HorizontalAlignment="Left" Margin="0,0,0,0" 
          Name="grdChars" VerticalAlignment="Top">
    <Grid.BitmapEffect>
        <DropShadowBitmapEffect></DropShadowBitmapEffect>
    </Grid.BitmapEffect>
</Grid>
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 seperate 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. 

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 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 contructor and timer_Tick() event handler.

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 are 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...!! 

License

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

About the Author

Agent__007
Software Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 [modified] PinmemberAlexander Sharykin24-Jan-14 1:57 
GeneralRe: My vote of 3 PinprofessionalAgent__00726-Jan-14 20:57 
QuestionImage missing PinmemberAmarnath S1-Jan-13 5:54 
AnswerRe: Image missing PinmemberSanjay_0071-Jan-13 23:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 28 Jan 2013
Article Copyright 2013 by Agent__007
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid