A guide to creating a calendar using DesktopX
Published on November 19, 2004 By _Martin_ In DesktopX Tutorials

DesktopX - How to ...

....... make a calendar

 

What is this about?

The aim of this document is to show you how to take a sample object and customise it to make it your own. In the process it will guide you through the script so you get a better understanding of how it does what it does.

 

Getting Started

This guide is based upon the object you can find here. The first thing you should do is download this zip file. Inside there is an exe file. If you right click the DesktopX icon you can select Import.

Click the Widgets radio button at the top of the screen. If you haven't previously imported this and it isn't on the widget list, then you can browse to find the exe file called DXCalendar.exe

Once you've done this then it will be on your desktop. If you then right click the DesktopX icon and select Object Navigator you will see all the objects that make up the calendar.

You are now in a position to start changing the object.

Throughtout this, I will be telling you what you need to do to edit the object in clear detail, but will not actually show an example of this. The main reason for this is that you will learn much better if you do it yourself. Hopefully, if the instruction is suitably clear then you should be able to do this.

 

The structure of the object

The key to editing an object is understanding the structure of the object and how the different parts interact. Typically the script can remain fundamentally unchanged when you are editing an existing widget so although we will discuss the script here for educational reasons the structure is the important element.

This shows the objects that are important in the object. There are other objects in the widget, but these are the ones to focus on; the others are created dynamically as required.

Calendar: This is the background image for the object and contains the key script for this object. It is the most important object in the widget.

Calendar_Month & Calendar_Year: These are simple text objects that display the month and year being displayed on the calendar.

Calendar_Today: This is the image the is displayed around the current date if the current month is displayed on the calendar.

Calendar_month_next & Calendar_month_prev: These two objects have scripts that allow you adjust the month being displayed to the previous or the next one.

Template_DayOfWeek & Template_Day: These are hidden objects that are the base from which the day of the week names and the day numbers are created. By editing these templates the appearance of the created objects for the days and numbers are edited.

Changing the background

Ironically, the most complicated element of editing this object is getting the background right; and even that is not hugely difficult!

If you look in your theme directory you will see a file called newcal_back.png which you can see on the right. You will note that it is actually much smaller vertically than the one you see on the screen. The reason for this is because different months have different numbers of weeks in them (4, 5 or 6). This object resizes according to the number of weeks that are being displayed. Let's look in more detail at the different elements of the the image.

If you replace the image with one of your own, there is one key skill that you need. That is the knowledge of how to stretch object in DesktopX. This is shown clearly in the section 3.1 of the Developers Guide, but we will expand on that a little here for this specific case.

What you need to do is open the properties for the Calendar object, and select the States tab. You will see that the at the bottom of the dialog is a button labeled "Advanced". Click that and you are ready to see how this works.

On this dialog you are able to choose how you would like to stretch or title the image used which is obviously what we need to do given that we are looking to specifically tile the area for the weeks as highlighted below.

Because we don't need to adjust the width in this scenario so we can leave the "Horizontal Resize" options as they are. So, the key thing is the "Vertical Resize".

What you have are 3 settings. You need to specify the area of the image which you want expanding. These in the dialog are represented by the C and D parameters.

If you look on the source image as expanded on the left, you will see why the values used are "39" and "65" are used. These represent the top and the bottom of the area we highlighted above as the area which displays the data for a given week.

You also need to specify whether you wish to stretch or tile the image. In the most cases, tiling will be the most appropriate option. Once you have edited the advanced properties in your new image to tile like this then the object should work perfectly.

Editing other elements

The rest of the elements in the calendar are all text objects. As such, changing the way they look is very easy.

If you open the two "template" objects you can quickly adjust the text of the days and dates to match the background you have. Even if you did something as simple as adjusting the hue of the original background then you will probably need to adjust the colors of these templates so that they complement the new background color.

You can also change the month and year text object in a similar manner. The image on the left shows the elements that you may wish to adjust for any of these elements. You can change the font and it's color. You may also choose to add a border around the object though unless the text is fairly big the border may be a bit domineering.

If you decide you want a bigger font you may find that you find it desirable to adjust the spacing of the dates and even make the background wider. We will look at this in the next section when we look at the script.

You can also add a drop shadow or a glow if that suits your design, and you can make the text semi transparent. This may be especially useful if you make your background object semi-transparent.

Editing the objects for moving to the previous or next month are the same except that you also need to edit the Mouse-over state. Although these objects will work perfectly well without a mouse over state it helps the user identify the fact that they can interact with these objects. You could even add a mouse down eddect if you wanted to further enhance the object.

In fact, there is no reason why these objects need to be text objects. You may find that it suits your deign better to make these images. Obviously for these you can also use multiple states to enhance to usability of them.

The final point to make is that the position of these objects is totally flexible. Unlike the days and dates which need to maintain there position, these can be placed anywhere on the background object. Simply hold down CTRL and drag the object to where you want it.

The power of script

The real power of script is that it can just sit there attached to an object and do it's job. You really don't need to have to deal with it. Viewing a well commented and well structures script however is a great way to start to learn how to use them if you are not experienced in scripting.

There are 3 scripts in this object and we will go through them all separately to see how they work.

Calendar script

This script's purpose is to provide the functionality to draw the calendar for any given month.

Dim DateString
Dim FirstDayOfWeek
Dim CurrentDay
Dim ColumnSpace
Dim RowSpace
Dim DaysRow
Dim FirstRow
Dim FirstColumn
Dim TodayOffsetX
Dim TodayOffsetY

This first part of the code simply declares variables used in the script. We will describe these variables when we get to them. For now it is enough to know that it is good practice to declare the variables before they are used.

Sub Object_OnScriptEnter

' Set Variables
ColumnSpace = 31  ' Gap between columns
RowSpace = 27     ' Gap between rows
DaysRow = 24      ' y coordinate of the days
FirstRow = 46     ' y coordinate of first row
FirstColumn = 29  ' x coordinate of first column
TodayOffsetX = -8 ' x offset of the today image from top left of current day text
TodayOffsetY = -11 ' y offset of the today image from top left of current day text

Here we set values for a lot of the variables we defined above. Earlier I mentioned how you may at times want to adjust the position of days and dates depending on the background object used and the font used. This is where you do it. Good scripts will always use variables where possible rather than hard coding values. This makes them far more flexible.

The parameters you can set are all clearly labeled and where x and y coordinates are used, these are relative to the top left of the background image. Try adjusting these values , and Applying the script to see the impact it has on the calendar. Don't worry if some of the dates appear off the calendar for now. You can vary the background image to allow for this.

To make a wider background object you can simply change the width parameter on the Summary tab. Because in the Advanced Properties for the image we set parameters for the object to stretch horizontally the object will still look correct.

To make a taller background object there are two elements you can change. You can obviously add extra space at the top or bottom of the object. However, if you are increasing to font spacing you really must make the size of the vertically tiled area larger by the same amount.

' Define current month and year e.g. September 2004 so we can start by showing this month
x = Now()
x = Month(x) & " " & Year(x)

' Define current date so we can monitor whether the day has changed
CurrentDay = Date()

' Define a timer to run every minute and check if the date has changed so we can update the calendar if it has
Object.SetTimer 1000, 60000

' Draw the initial calendar for this month
DrawCalendar(x)

The commenting here should be fairly explicit. We get information on the current system date and then we call the DrawCalendar function which we will come onto passing a parameter of month and date like "9 2004" telling it what month to draw.

Also in here we define a timer to run every minute. We will look at this timer now before we move onto looking at the main DrawCalendar function.

Sub Object_OnTimer1000
  ' Determine if the current date has changed
  If Date() <> CurrentDay Then
    ' If it has, update the current day and redraw the calendar based on the new current day
    CurrentDay = Date()
    x = Now()
    x = Month(x) & " " & Year(x)
    DrawCalendar(x)
  End If
End Sub

What we do here is look at the current date and compare it to the date stored in CurrentDay which we just defined. Obviously in this case because we just set CurrentDay to Date() the two will be equal so nothing will happen, but if you leave this object running and the clock passes midnight to a new day then the code in the "If" statement will be triggered. What this does it to update CurrentDay to the new date and redraw the calendar. for the current month. Obviously on most days this will just redraw the current month, but if the day changes to a new month it ensures that the calendar draws the new month.

OK, now onto the main function that draws the calendar ...

Function DrawCalendar(thisdate)

  ' Get first day of week from system settings
  If Weekday(MonthName(8) & " 7, 2004") = 7 Then
    FirstDayOfWeek = 1
  Else
    FirstDayOfWeek = 2
  End If

Your computer will either consider Monday or Sunday the first day of the week depending on your personal settings. I know that August 8th 2004 was a Sunday. What this script is to check what day of the week the computer thinks that date is. From that I can define a variable called FirstDayOfWeek. If it thinks that date is the 7th day of the week then I Set FirstDay of week to 1 (Monday) otherwise I set it to set this to 2 (Sunday). This becomes important at several points later in the script.

Note that I use the function MonthName(8) rather than explicitly writing "August" because this would cause errors on non-English machines. MonthName will return a name that the computer understands like Aout (for French computers.

' Create a string of the first letter of each day
For x = 1 To 7
  DayString = DayString & Left(WeekdayName(x,False,FirstDayOfWeek),1)
Next

At some point I need to create text labels for the days of the week. The way I do this is to build a string of the first character of the day name. I do this 7 times until I have a 7 character string. Note that the FirstDayOfWeek variable comes into play here, because the VBScript WeekdayName function requires that you specify which is the first day of the week so it can return the correct letter. The variable DayString will either contain "MTWTFSS" or "SMTWTFS" (assuming an English computer) depending on the FirstDayOfWeek setting.

' Delete existing days of week if present
For x = 1 To 7
  If DesktopX.IsObject("DayOfWeek" & x) Then
    DesktopX.Object("DayOfWeek" & x).Delete
  End If
Next

' Create new days of week
For x = 1 To 7
  DesktopX.Object("Template_DayOfWeek").Clone "DayOfWeek" & x, FirstColumn + ((x - 1) * ColumnSpace), DaysRow
  DesktopX.Object("DayOfWeek" & x).Text = Mid(DayString, x, 1)
  DesktopX.Object("DayOfWeek" & x).Visible = True
Next

What this is doing is to check for any existing day of the week objects and deleting them if they exist. The name of the objects for these days are DayOfWeek1, DayOfWeek2 ... DayOfWeek7 so it loops and checks for objects with these names and deletes them if required.

We then have a fairly cool bit of script which achieves a lot in 5 lines, mainly because of good use of variables and templates. We want to create 7 objects so we loop 7 times. Each time we do three things:

1) We Clone the template object for the weekdays, There are three parameters. The first is the name for the new object which we build by concatenating the string "DayOfWeek" with the number of the loop we are on. The next 2 parameters are the x and y coordinates to position the object. We use maths here based on the variables defined earlier. While the maths may look quite complicated it is really fairly simple especially as we gave the variables sensible names! This is also far more flexible as we mentioned earlier than hard coding numbers in here.

2) We then set the text of the object to be the first letter of that day of the week. We get this by using the Mid function to extract a single character from the DayString we build earlier. The character we take is determined by the position in the loop.

3) Finally we make our new object visible which we need to do because the template object we cloned was hidden

' Delete existing day objects if they exist
For x = 1 To 42
  If DesktopX.IsObject("Day" & x) Then
    DesktopX.Object("Day" & x).Delete
  End If
Next

Just as we did for the days of the week, we delete any day objects drawn so we can start fresh.

' Determine the month and year that we are drawing from the variable we passed to the function
thismonth = Left(thisdate, InStr(thisdate," ") -1)
thisyear = Right(thisdate, Len(thisdate) - Len(thismonth) - 1)

' Set the year text object
DesktopX.Object("Calendar_Year").Text = thisyear

' Get the month name and write it to the text object for the month name
thisMonthName = MonthName(thismonth)
DesktopX.Object("Calendar_Month").Text = thisMonthName

What this script does is extract the month and year from the variable passed to the function and stores them in the variables thismonth and thisyear. The year text object "Calendar_Year" is then set to the year in thisyear. We then get the name of the month in the variable thismonth using the MonthName function, and set the "Calendar_Month" text object to this value.

' Check which day number in the week the first day of the month occurs on
calFirstDay = thisMonthName & " 1, " & thisYear
calFirstDay = CDate(calFirstDay)
calFirstDay = DatePart("w", calFirstDay, FirstDayOfWeek)

' Check how many days there are in the current month
calDaysInMonth = DaysInAnyMonth(thisMonth, thisYear)

If order to accurately draw the month there are two key things we need to establish. We need to know which day of the week the first day of the month is so we know where on the calendar to draw the first day. We then need to know how many days there are in the month so we can draw them. This script builds a string of the first day of the current month e.g. "September 1, 2004". It then ensures it is in a Date format and uses the DatePart VBScript function to find out which day of the week this day is on. Note that again we need the FirstDayOfWeek variable again to ensure that the day of the week returned is appropriate for the user's computer settings.

We then use the DaysInAnyMonth function at the end of the script to determine the number of days in the month we are drawing.

' Check how many days there are in the previous month as we are going to be drawing these
If calFirstDay > 1 Then
  If thismonth = 1 Then
    dayinprevmonth = DaysInAnyMonth(12, thisyear - 1)
  Else
    dayinprevmonth = DaysInAnyMonth(thismonth - 1, thisyear)
  End If

  'Create days from the previous month from the calendar beginning to just before where the current month starts
  For x = 1 To calFirstDay - 1
    ' Clone the hidden template object and position based on the variables at the top
    DesktopX.Object("Template_Day").Clone "Day" & x, FirstColumn + (ColumnSpace * (x-1)), FirstRow
    DesktopX.Object("Day" & x).Text = dayinprevmonth - calFirstDay + 1 + x
    ' make them semi transparent and then show them
    DesktopX.Object("Day" & x).Opacity = 30
    DesktopX.Object("Day" & x).Visible = True
  Next
  Set daysinprevmonth = Nothing
End If

Before we draw the days from this month we draw the last few days from the previous month in any space prior to where the first day is drawn. e.g. if the first day is on a Thursday then we can draw the previous month days in the spaces up to Wednesday.

The first part of the above script calculates what the previous month is and then gets the number of days in that month.

After that it clones the Template_Day object and positions it much as we did for the days of the week. It then works out what day that object was and sets the text appropriately. The opacity of the of these objects is lowered so they are less visible than the current month which is more important. The object is then shown.

If you wanted days from other months to look fundamentally different than the current month days then you could create a new Template object which looks as you want it and clone that instead.

'Draw days for this month
For x = calFirstDay To calFirstDay + calDaysInMonth - 1
  DesktopX.Object("Template_Day").Clone "Day" & x, FirstColumn + (ColumnSpace * ((x-1) Mod 7)), FirstRow + (Int((x-1)/7)*RowSpace)
  DesktopX.Object("Day" & x).Text = x - calFirstDay + 1
  DesktopX.Object("Day" & x).Visible = True
Next

Now onto the current month. This again is a cloning exercise that you will be familiar with by now. The maths looks complex on me so just trust me that it positions the object on a new line if appropriate!

'Calculate the number of rows required based on whether a date has been create on the first instance of every row
If DesktopX.IsObject("Day22") Then RowCount = 4
If DesktopX.IsObject("Day29") Then RowCount = 5
If DesktopX.IsObject("Day36") Then RowCount = 6

' Scale the background to allow correct number of weeks to be shown
Object.Height = 72 + (27 * RowCount)
' Reposition the year relative to the bottom of the object
DesktopX.Object("Calendar_Year").Top = Object.Height - 33

We are now about to draw the objects for the next month, but before we do we we need to ensure that the object is the right height. We only want it to be as big as it needs to be to show the current month. We check to see if various objects exist. These would be the first object on each row so we can tell how many rows of days there are but seeing if these objects have been created.

Once we know this we can scale the background accordingly. This will tile the correct area we defined at the beginning and draw the image as required. We then reposition the object displaying the year relative to the bottom of the object.

'Draw days for next month to the end of the last row
For x = calFirstDay + calDaysInMonth To RowCount * 7
  DesktopX.Object("Template_Day").Clone "Day" & x, FirstColumn + (ColumnSpace * ((x-1) Mod 7)), FirstRow + (Int((x-1)/7)*RowSpace)
  DesktopX.Object("Day" & x).Text = x - calFirstDay - calDaysInMonth + 1
  DesktopX.Object("Day" & x).Opacity = 30
  DesktopX.Object("Day" & x).Visible = True
Next

Now, just a final cloning run to add the few days from the beginning of the next month that will fit on the row.

  ' Check to see if the month being shown is the current month
  If DesktopX.Object("Calendar_Month").Text = MonthName(Month(Now())) And CInt(DesktopX.Object("Calendar_Year").Text) = Year(Now()) Then
    ' If it is then position and show the today image
    x = calFirstDay + Day(Now()) - 1
    DesktopX.Object("Calendar_Today").Move DesktopX.Object("Day" & x).Left + TodayOffsetX, DesktopX.Object("Day" & x).Top + TodayOffsetY
    DesktopX.Object("Calendar_Today").Visible = True
    DesktopX.Object("Day" & x).OnTop
  Else
    ' If not then hide the today image
    DesktopX.Object("Calendar_Today").Visible = False
  End If
End Function

This little section of code is designed to draw the image which highlights "today" if the current month is being shown. The "if" routine checks the month and year objects against the current date. If the current month is being shown then it works out which object represents "today". It then moves the today image relative to the today object using the offset variables defined at the beginning of the script. The object is shown and moved to the top of the z-order. This moving of the z-order is because I want it to look as if the circle has been drawn over the number. If you have a "today" image that you want to appear behind the date you can remove or comment out this object.

That's it we're done drawing the calendar!

' Function to return the number of days in a month for the month and year provided
Function DaysInAnyMonth(intMonth, IntYear)
  If IntYear = 0 Then
    IntYear = Year(Now())
  End If

  Select Case (intMonth)
    Case 2 ' February (includes leap year calculation)
      If (IntYear Mod 4 = 0) And (IntYear Mod 100 <> 0) Or (IntYear Mod 400 = 0) Then
        DaysInAnyMonth = 29
      Else
        DaysInAnyMonth = 28
      End If
    Case 4, 6, 9, 11
      DaysInAnyMonth = 30
    Case Else
      DaysInAnyMonth = 31
  End Select
End Function

This little bit of script is what we called several times earlier to get the number of says in a given month. The first part of the script checks that a year variable was passed. It uses the current year if it isn't given buy we're always good and provide it anyway.

It then uses Select Case to do different things depending on the month. The first thing it does is run a scenario for February (month 2). It checks for a leap year and then returns the correct number of days. This is why passing the year was important in case you were wondering. It then returns 30 days for April, June, September and Novemeber and returns 31 for any other month.

Month changing script

These two objects both have basically the same simple script. We will go through the script for the previous month below.

' If the mouse click is released over the object
Function Object_OnLButtonUp(x,y,dragged)
  ' It's good habit to only execute script if the user is not trying to drag the object
  If dragged = False Then
    ' Create a date based on the month and year strings
    x = DesktopX.Object("Calendar_Month").Text & " 1, " & DesktopX.Object("Calendar_Year").Text

    ' Identify the month and year
    x = Month(x) & " " & Year(x)
    xmonth = CInt(Left(x, InStr(x," ") - 1)) - 1
    xyear = CInt(Right(x, Len(x)-InStr(x," ")))

    ' Calculate what the previous month would be
    If xmonth > 0 Then
      x = xmonth & " " & xyear
    Else
      x = "12 " & xyear - 1
    End If

    ' Redraw the calendar
    DesktopX.ScriptObject("Calendar").DrawCalendar(x)
  End If
End Function

Again, the commenting should make this quite clear. The function executes when the left mouse button is released over the object. It checks that the user was actually clicking and not dragging the object and then runs the script.

The script creates a date string based on the month and year text strings. From that date string it can then get a month number and year that the DrawCalendar function would recognise. Before they are passed however a calculation is done to work out the previous month. The script for the next month object just does the equivalent to get the next month.

The routine to redraw the calender is then called passing the appropriate month and year.

In Summary

We have seen here that the object is structured very simply. You don't need to worry at all what the script is doing if you don't want, you can simply change the background image and the style of a few text objects if required. Through just this you have incredible flexibility in the object.

Even if you do want to do something a bit more complex with the positioning of the text objects all you need to do is adjust the values of a few variables at the beginning of the script and the script will work the rest out from there.

I hope you've found this useful. I'll see what I can come up with for the next time.


Comments (Page 2)
2 Pages1 2 
on Aug 29, 2008
Thank you for this nice article :
on Jun 29, 2010

to try this out some day or year

2 Pages1 2