Introduction
Sidebar gadgets are one of the gems of the Windows Vista operating system.
For developers, they open some innovating, exciting possibilities, and provide one
more reason why being a developer is fun. But wouldn't you know it?
Microsoft left a few important features out of version 1, and this article will
solve one of them: the ability to share locally-stored data between gadget instances.
In this article we'll look at a solution to that problem that uses the time-tested
.ini file format first used in Windows 1.0 back in 1985.
Now, some people are aghast at the thought of using anything other than XML
to save settings in a file. I did in fact consider using XML,
but ultimately decided to go with the .ini format for a few key reasons:
- Simplicity. The majority of gadgets will have relatively modest needs, and
will likely need to store string values.
XML's main advantage
is its ability to handle more complex storage requirements, but at the expense of
simplicity (at least when compared to an .ini file).
- User-editable. Let's face it: manually editing an
XML file is
a place where only geeks will go. .ini files, on the other hand,
are simple to understand and edit - even for a newbie.
- Readable.
.ini files are much easier and quicker to read, and
inserting comments is simple and painless.
- Version-proof code. To parse the
.ini file, the code in this
article uses basic RegExp notations and string manipulations, things
that will likely execute unchanged many years from now, whereas XML
support in JavaScript is only for advanced coders, and is constantly
changing.
Plainly speaking, an .ini file is just right for the job at hand, and
although the same thing can be accomplished using XML, it was my sense
that using XML would be overkill.
Lastly, as you'll see below, Windows Vista itself uses the .ini file
format for storing global gadget settings, so by doing so ourselves, we are remaining
consistent with the operating system.
Note: It also would have been possible to store settings in the
Windows registry, but that would limit the portability and ease-of-use of the settings,
so it was quickly ruled out.
Gadget settings
Gadgets behave differently than regular programs, and in fact are not programs in
the traditional sense. The only program to speak of is the Sidebar application
itself, which by default is set to execute when Windows boots. The Sidebar
is basically a kind of Web browser, and the gadgets are Web pages that are open
and displayed simultaneously.
When you add a gadget to the Sidebar, the Sidebar application creates persistent
storage for that gadget in a file located in the current user's personal folder.
The exact path of the Sidebar storage file is C:\Users\<user
name>\AppData\Local\Microsoft\Windows
Sidebar\Settings.ini.
In the Sidebar's Settings.ini file, the newly-added gadget is assigned
an integer identifying number (for example, "5"), and all the settings for that
gadget are stored in a group using that number (for example, [Section 5]).
Within each group, settings are stored as key/value pairs in typical .ini
fashion: <key>=<value>.
The Sidebar saves some basic settings for each gadget currently being displayed,
such as the gadget's path, its docked/undocked status, the opacity setting, etc.
The key names for all of the Sidebar-managed settings start with the string,
PrivateSetting_.
For example, the opacity setting is stored using the key name,
PrivateSetting_GadgetOpacity.
As a developer, you can add settings to the .ini file too, using the
built-in System.Gadget.Settings JavaScript object.
Table 1: Windows Vista built-in gadget settings model
|
Method
|
Action
|
read(strKey)
|
Read the stored setting that corresponds to strKey,
and attempt
to guess the correct data type to convert the value to. Because this can
produce
rounding errors with numeric values, as well as other data conversion
errors,
readString() should generally be used instead of this
method.
|
readString(strKey)
|
Read the stored setting that corresponds to strKey,
and return
the value as a character string. In most cases, this method should be
used,
rather than the read() method.
|
write(strKey, anyValue)
|
Convert the value anyValue to a string, and then
save it under
the key name strKey. To avoid the errors that can
occur
when JavaScript converts non-string data, you should use
writeString()
instead of write() whenever possible.
|
writeString(strKey, strValue)
|
Save the string value strValue under the key name
strKey.
In most cases, this method should be used, rather than the
write()
method.
|
As indicated in Table 1, readString() and writeString()
should almost always be favored over the read() and write()
methods. (The word "almost" is used in case there is an instance where read
or write should be used, but I haven't thought of one yet.)
If you try to store an Object data type, the word "object" will be stored,
not the
enumerated contents of the object. Therefore, in order to store an Object
data type it needs to be converted to the JSON string representation, and to
do
that I'd recommend using the public domain json.js library, developed by Douglas
Crockford (http://www.json.org).
It is beyond the scope of this article to get into any great depth with json.js,
but suffice to say that you can easily store an Object data type using the following
syntax.
Limitations of the built-in gadget settings
As you would expect, there are inherent limitations involved with storing settings
using the built-in methods, but the limitations may be more restrictive than you
would initially guess.
- There is a limit of 2,048 characters for each setting. I could not find any
documentation of this fact; I figured it out through trial and error (and through
some painful debugging sessions). Normally this does not pose a serious
limitation,
but if you try to store complex objects using the technique described above, you
may run into this limitation, as I did.
- When you close a gadget (i.e., remove it from the Sidebar by clicking the "X" icon),
the Sidebar removes all of the settings for that gadget from the Sidebar's
Settings.ini
file. There is no way for settings to survive a closed gadget.
- Because each gadget instance stores its settings in a separate section in the
Sidebar's
Settings.ini file, there is no way to share settings between gadget
instances.
- There is no built-in method of "packaging" default settings along with a gadget,
or to ship different versions of a gadget with different default settings.
(Other than hard-coding the defaults into JavaScript code.)
With these limitations in mind, we arrive at the subject of this article: the Gadget
Settings Manager.
Gadget Settings Manager
The solution to the limitations imposed by the Sidebar Gadget.Settings
object is
the creation of a Settings.ini file located in the Gadget's root folder,
combined with a small JavaScript library to manage the settings contained
within
it.
The Settings.ini file created by SettingsManager is structured
exactly
like the Windows .ini files you are probably accustomed to using.
In fact, while researching the topic during development of this library, I dusted
off my copy of The Waite Group's Windows API Bible and made sure
it would be compatible. However, I utterly refused to call the settings retrieval
method GetPrivateProfileString().
For those unacquainted with .ini file structure, it is a basic ASCII
text file containing key/value pairs organized into sections, called "groups".
Any text that starts with a semi-colon (;) or pound sign (#)
is considered a comment, and is ignored. There is no multi-line comment syntax.
Listing 1: Structure of an .ini file
You can find more information about the .ini file format at
http://en.wikipedia.org/wiki/INI_file. The SettingsManager
values
are always surrounded by double-quotes, which is the same behavior as the Sidebar's
Settings.ini string values. However, the surrounding double-quotes
are not included as part of the returned value.
Getting started
To get started with the SettingsManager, a download link for the
SettingsManager.js
file can be found below in the Downloads section below.
The file should be placed into whichever folder you are placing "core"
JavaScript
files for your gadget, because it needs to be accessible to every page that needs
to read and/or write settings, and it is language-independent. Then, in the
<head /> section of each page that needs to access settings,
place a <script /> tag referencing the SettingManager.js
file.
Replace core/js/ with the actual folder name in which
SettingsManager.js
resides. As with all src references in Sidebar gadgets, do not make the path
absolute by inserting a leading slash character. The path should be relative
to the root folder.
The SettingsManager library will not interfere or conflict with any other
JavaScript
code in the gadget because it is entirely encapsulated in one globally-scoped object,
called SettingsManager. It maintains storage of all the in-memory
settings within that object, as well as all the methods for accessing and changing
the settings.
Working with SettingsManager
Before accessing any settings, you first need to instruct SettingsManager to
load
all the settings from the gadget's Settings.ini file into memory.
That is done using the loadFile() method. (If you don't first
load the setting, then you run the risk of over-writing any settings currently in
the Settings.ini file.)
Once the settings are loaded, you can retrieve any of the settings; you can add,
update, and delete settings; and you can get lists of group names and key names.
See the method reference table below for documentation of each method.
All settings are manipulated in memory, not on disk. Settings are not saved
back to the Settings.ini file until the saveFile() function
is called. This allows you full control over how and when file access is performed.
Listing 2 contains a typical interaction with SettingsManager.
saveBoxSettings()
is a hypothetical function in the gadget to save the current position and size of
an element in the .ini file.
Listing 2: Typical SettingsManager usage
Using the example function in Listing 2, let's call the function with sample arguments
and see what the resulting Settings.ini file would look like.
Sample function call:
Contents added to the Settings.ini file:
Property and Method Reference
The following tables list all of the available properties and methods of the
SettingsManager
library. Remember, property and method names are case-sensitive.
Table 2: Properties available in the SettingsManager library
|
Property
|
Description
|
FileName
|
Name of the file used to store the settings. Does not include any path
information,
but does include the extension (.ini). The default value is
"Settings.ini". If working with more than one settings
file, this
property controls which settings file you are currently loading or
saving.
Changing this property has no direct effect on the settings in memory.
|
LocalFolder
|
The sub-folder where the settings file is to be found/stored. Needs to be
expressed relative to the gadget root. For example, if the settings are
to
be stored as <gadget root>\core\settings\Settings.ini,
then
LocalFolder should be set to "core\settings".
The
default value is an empty string (""), which stores the
settings in
the gadget root.
|
Table 3: Methods available in the SettingsManager library
|
Method
|
Usage and Description
|
getFullPath
|
Constructs and returns the full file system path of the settings
file, including
drive letter, gadget path, LocalFolder, and
FileName.
|
loadFile
|
Loads all the settings from the .ini file into
memory. This is
normally the first method called before adding, changing, and
deleting settings.
|
saveFile
|
Saves all settings in memory to the .ini file,
completely overwriting
the existing file.
|
getValue
|
Retrieves the value of one setting from memory. If the
combination of groupName
and keyName is not found, then
defaultValue is returned,
or an empty string ("") is returned if
defaultValue is
not passed.
|
setValue
|
Sets one setting's value in memory. The value is not save to the
.ini
file on disk until saveFile() is called. If
groupName
does not exist, it is created. If keyName
exists in
the group it is overwritten; otherwise it is created.
value must
be a string.
|
deleteValue
|
Deletes one setting from memory. The value is not deleted from
the .ini
file on disk until saveFile() is called. If the
group is empty
after deleting the value, the group itself will be deleted if
deleteEmptyGroup
is true.
|
deleteGroup
|
Deletes one group from memory, and all values stored in that
group. The changes
are not saved to the .ini file on disk until
saveFile()
is called.
|
getKeyCount
|
Returns the number of keys [i.e., settings] stored within the
specified group in
memory.
|
getKeyNames
|
Returns an array of strings containing all of the key names
within the specified
group in memory.
|
getGroupCount
|
Returns the number of groups currently stored in memory.
|
getGroupNames
|
Returns an array of strings containing all of the group names
currently stored in
memory.
|
isValidName
|
Returns true if name is a valid group
or key name.
(Group and key names have the same criteria for validity: at
least one character
in length, must start and end with a letter, number, or
underscore, and in between
can be almost any combination of letters, numbers, spaces, and
punctuation.)
|
getIniString
|
Generates and returns all of the settings in memory as a string,
in .ini
file format. The return value is exactly the same as the contents
of the
.ini file would be. In fact, this method is used
by saveFile()
to generate the contents of the .ini file that is
saved. This
method is exposed since it may have additional value apart from
saving to disk.
|
One at a time, please!
Under most circumstances, SettingsManager will be used infrequently - for
example, when the user clicks a Save button to persist the current settings.
Realistically,
that means we don't need to worry about more than one gadget instance trying to
save changes to the .ini file at the same time. Thus, the
SettingsManager
was purposely designed to ignore that possibility. Rather than adding the
bulk and complication of a proprietary locking scheme I decided to keep the library
size smaller and place the onus of a locking mechanism on the small percentage of
programs that require them.
In others words, why increase the size and complexity of 100% of the gadgets, if
only 5% will require it?
However, there are some situations in which a locking mechanism is critical. For
example, there may be a case where several gadget instances are active simultaneously,
and the instances use SettingsManager to communicate with each other. In
this
case it is important that only one instance of the gadget be allowed to write to
the .ini file at any one time.
In this example, a gadget instance would use a locking mechanism in the following
manner:
- Obtain a lock,
- Load the settings from disk, change the setting(s) in memory, and then immediately
save them back to disk, and,
- Release the lock.
The most important design aspects of any such locking mechanism are:
- The locking mechanism must be globally available to all gadget instances, and,
- The program code must obtain (or deny) the lock in one single operation -
within one line of code - so that there is no chance that two instances could
simultaneously obtain a lock.
Building an actual locking mechanism is beyond the scope of this article, but I
did build a generic LockManager module, which I may include in a future
article.
(If there is interest, please leave me a comment!)
How it works
So now that we know how to use SettingsManager, let's see how it works.
Since
developers tend to be most interested in the under-the-cover details, I'll start
inside and work my way out.
ActiveX objects
One of the nice aspects of Windows Vista gadget development is that you don't need
to worry about which browser you're developing for, because everything runs on IE7
in the Sidebar. Another helpful thing is that the Sidebar has a high level
of trust of the installed gadgets, so permissions are rarely an issue. Both
of these helped a great deal in developing SettingsManager, because
ActiveX objects
could be used with confidence. It is a liberating experience to code an HTML
page without fear of browser incompatibility!
ActiveX objects are used in the loadFile() and
saveFile()
methods, to create a Scripting.FileSystemObject. Once a
FileSystemObject
is created, reading and writing data to disk is straight-forward. For example,
the saveFile() method consists of the following logic.
Listing 3: saveFile() program logic
To provide control over when file access takes place, SettingsManager only
touches
the file system during loadFile() and saveFile().
All other operations, such as reading and writing individual settings, take place
in memory.
Storage in memory
Storing the settings in memory is accomplished using arrays and objects. One
private property, SettingsManager._groups, is used to store all settings.
(In my coding standards, private properties and methods start with an underscore.)
Even though JavaScript objects do not have true private
members, accessing members
marked private from your program code is strongly discouraged. The
_groups
property is considered private for all the reasons one would make a private property
in a class: the internal structures can be changed without affecting programs that
rely on them, and the built-in methods provide all the functionality necessary under
normal circumstances.
_groups is an array of JavaScript objects, with each element of
the
array representing one group of settings within the .ini file.
(As explained in Gadget Settings Manager above, a group is all
the settings that come after the [Group Name] notation in the file.)
Each element of the _groups array, called a "group object", is defined
using JSON as:
As you can see, each group object has two properties - Name and
Values. The Values property is an array of Key/Value
objects, used to store all the settings within the group.
Apart from simple assignment statements, the arrays are manipulated in the
SettingsManager
methods using the splice() function. This handy JavaScript
function
can be used to delete elements, insert elements, or both at the same time.
Bursting performance
The loadFile() method is used to fill the _groups array
with the settings stored in the .ini file. I have not done actual
measurements and comparisons of different loading methods, but the techniques used
in loadFile() should produce very quick load times.
Recognizing that I/O is normally the most restrictive bottleneck in an application,
all of the data is read at once using the FileSystemObject.ReadAll()
function. I have found in real-world usage that all-at-once I/O functions
like ReadAll() can greatly improve performance, just as any "bursting"
operation would.
After the file contents are in memory, a simple RegExp match()
call puts all the non-blank, non-comment lines into an array, one element per line.
Then, it is a matter of iterating through the elements and building the
_groups
array. Again, regular expressions are employed to analyze the text on each
line.
First, a line is tested to see if it contains a group name, in which case it creates
a new element in the _groups array. Or, if not, the line is tested
to see if it contains a Key/Value pair. If so, it adds the Key/Value pair
to the Values array in the last group added to the _groups
array.
The group and key/value tests are defined as follows. (_validNamePattern
is defined as a private property so that the group and key naming
requirements can
easily be changed in one place.)
JavaScript doc comments
Each method in SettingsManager is fully commented, using Microsoft's
IntelliSense-compatible
format. This will allow a developer using the "Orcas" version of Visual Studio
to get full IntelliSense support with their JavaScript files.
For example,
if you type SettingsManager. the full list of available properties
and methods in SettingsManager will appear. Then, if you select
getValue(),
the list of that method's arguments and their types will be displayed.
I believe once developers start seeing the tremendous value of IntelliSense
support
in JavaScript, this comment style will flourish. You can find a thorough
write-up
of the specification for "JavaScript doc comments" on Bertrand Le Roy's blog post.
Integration
Finally, the SettingsManager is implemented as one large JSON
(JavaScript Object Notation)
variable, which is a common technique employed by third party library makers.
It is similar to declaring a class in C# and other object-oriented
languages.
The most important reason for using this technique is because only the module name
itself (in this case SettingsManager) is exposed to the global namespace.
Thus, assuming your JavaScript code does not use the name
SettingsManager
in the global namespace, the module can be integrated without fear of naming conflicts.
Summary
SettingsManager fills a gap in the Windows Vista Sidebar gadget object
model: the
ability to share settings between gadgets, as well as to "package" a gadget with
default settings. The module reads and writes the standard Windows .ini
file format, making it simple to directly edit settings while maintaining compatibility
with Sidebar gadget standards.
Implementing SettingsManager is accomplished with the simple addition of a
<script
/> tag at the top of any page that needs access to settings.
SettingsManager should not introduce any naming conflicts, because its
JavaScript
code is encapsulated in a single object called SettingsManager.
SettingsManager does not have any built-on locking mechanism, which is fine
for
95% of its uses, but for the small percentage of applications that require a locking
mechanism, that code will have to be supplied and managed external to
SettingsManager.
(Don't forget to leave me a comment if you are interested in having me include
LockManager in a future article!)
When reading and writing settings, the most important thing to remember is that
settings are not saved to disk until the saveFile() method is called.
Keeping that concept in the forefront of your thoughts may save a great deal of
debugging time later.
I am interested in hearing from you! Please take a moment to leave a comment
to let me know if this article was useful for you, and what you have used
SettingsManager
for in your gadget applications.
Download
References
About the author
Todd Northrop is the owner and creator of Lottery
Post, the Internet's largest lottery community. He founded his company,
Speednet Group, to perform consulting, web development, and web hosting, working
primarily with Microsoft .NET technologies.
Top Articles in this category
SuperToolTip
Office 2007 offers great new features, one of them is the SuperTooltip which provides much more information about controls than standard old style tooltips. This article shows how to build tooltips in such a way.
ORM in .NET 3.5
This article covers a general introduction to ORM concepts, the approach that .NET 3.5 takes, and how it compares to these other packages.
Barcode image generation made easy
In this article we are going to generate barcodes in .NET. Barcode-aware devices, such as scanners and printers, are readily available on the market, as long as the barcodes we generate follow the standards.We will implement barcode generation using Microsoft Ajax in a web application.
Introduction to 3-Tier Architecture
Brian Mains explains the benefits of a 3-tier architecture.
Foundations of Programming - Part 3 Persistence
The Foundations of Programming series looks at a number of key concepts, techniques and tools specifically designed to help developers meet the growing complexity of enterprise systems. Based on proven principals like unit testing, domain driven design, dependency injection and O/R Mappers, the series is aimed at developers interested in helping themselves.
|
|
Please login to rate or to leave a comment.