August 15, 2008

ActiveX Stopwatch Timer Control

Here's an example of a StopWatch control implemented in Csharp and used as a control on a web client. The control needs to be register on the clients machine then added to the clients web application (in html) and then called from Javascript.

<OBJECT id="activeXTimerControl" name="activeXTimerControl" 
        classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" 
        style="width: 504px; height: 182px" 
       codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
       </OBJECT>

To start the Stopwatch call Start() and to end call End() then call Time() to return the result as a string or use the AddTimeToGrid to add the time to a DataGrid Control (supplied by the StopWatch control) or call AddTimeToLabel which adds the time to a textbox.
activeXTimerControl.Start();
///do someting
activeXTimerControl.Stop();
var time = activeXTimerControl.Time;
activeXTimerControl.AddTimeToGrid("my timer", time);
activeXTimerControl.AddTimeToLabel("my timer", time);

Here's the full csharp code and at the end the full html


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace ActiveXTimerControl
{
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7")]
public partial class TimerControl : UserControl, IActiveXTimerControl
{
public TimerControl()
{
InitializeComponent();
}
}
}


namespace ActiveXTimerControl
{
public interface IActiveXTimerControl
{
///
/// Add resulting timer name and time (as a string) to the GridView.
///
///
///
void AddTimeToGrid(string timerName, string time);
///
/// Add the resulting timer name and the time (as a string) to the Label.
///
///
///
void AddTimeToLabel(string timerName, string time);
///
/// Start the timer.
///
void Start();
///
/// Stop the timer, call Start() before this.
///
void Stop();
///
/// Get the Time between the Start() and Stop() in milliseconds.
///
string Time { get; }
}

partial class TimerControl : IActiveXTimerControl
{
///
/// Required designer variable.
///
private System.ComponentModel.IContainer components = null;

///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Component Designer generated code

///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this._timerDataGridView = new System.Windows.Forms.DataGridView();
this._timerNameColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this._timerTimeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this._timerLabel = new System.Windows.Forms.Label();
this._timerTextBox = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this._timerDataGridView)).BeginInit();
this.SuspendLayout();
//
// _timerDataGridView
//
this._timerDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this._timerDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this._timerNameColumn,
this._timerTimeColumn});
this._timerDataGridView.Location = new System.Drawing.Point(6, 108);
this._timerDataGridView.Name = "_timerDataGridView";
this._timerDataGridView.Size = new System.Drawing.Size(261, 106);
this._timerDataGridView.TabIndex = 0;
//
// _timerNameColumn
//
this._timerNameColumn.HeaderText = "Timer Name";
this._timerNameColumn.Name = "_timerNameColumn";
//
// _timerTimeColumn
//
this._timerTimeColumn.HeaderText = "Time (msec)";
this._timerTimeColumn.Name = "_timerTimeColumn";
//
// _timerLabel
//
this._timerLabel.AutoSize = true;
this._timerLabel.Location = new System.Drawing.Point(223, 78);
this._timerLabel.Name = "_timerLabel";
this._timerLabel.Size = new System.Drawing.Size(129, 13);
this._timerLabel.TabIndex = 1;
this._timerLabel.Text = "Timer Name | Time (msec)";
//
// _timerTextBox
//
this._timerTextBox.Location = new System.Drawing.Point(6, 20);
this._timerTextBox.Multiline = true;
this._timerTextBox.Name = "_timerTextBox";
this._timerTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this._timerTextBox.Size = new System.Drawing.Size(261, 55);
this._timerTextBox.TabIndex = 2;
this._timerTextBox.Text = "Timer Name | Time (msec)";
//
// TimerControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this._timerTextBox);
this.Controls.Add(this._timerLabel);
this.Controls.Add(this._timerDataGridView);
this.Name = "TimerControl";
this.Size = new System.Drawing.Size(383, 313);
((System.ComponentModel.ISupportInitialize)(this._timerDataGridView)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();

}



#endregion



public void Start()
{
if (_stopWatch == null)
_stopWatch = new System.Diagnostics.Stopwatch();
_stopWatch.Start();
}

public void Stop()
{
_stopWatch.Stop();
}
public string Time
{
get
{
return string.Format("{0}", _stopWatch.ElapsedMilliseconds);
}
}

private System.Diagnostics.Stopwatch _stopWatch;

private System.Windows.Forms.DataGridView _timerDataGridView;
public void AddTimeToGrid(string timerName, string time)
{
if (_timerDataGridView == null)
_timerDataGridView = new System.Windows.Forms.DataGridView();
_timerDataGridView.Rows.Add(timerName, time);
this.Refresh();

}

private System.Windows.Forms.DataGridViewTextBoxColumn _timerNameColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn _timerTimeColumn;

private System.Windows.Forms.Label _timerLabel;
public void AddTimeToLabel(string timerName, string time)
{
//if (_timerLabel == null)
// _timerLabel = new System.Windows.Forms.Label();
//_timerLabel.Text += ( "\n" + timerName + " " + time);
//this.Refresh();
if (_timerTextBox == null)
{
_timerTextBox = new System.Windows.Forms.TextBox();
_timerTextBox.Text = "Timer Name | Time (msec)";
}
_timerTextBox.Text += ("\r\n" + timerName + " " + time);
this.Refresh();
}

private System.Windows.Forms.TextBox _timerTextBox;

}

}




The aspx:



<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>

<OBJECT id="activeXTimerControl" name="activeXTimerControl" classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" style="width: 504px; height: 182px" codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
</OBJECT>


<form name="frm" id="frm">
<input type=button value="Click me" onClick="doScript();">
</form>


</body>

<script language="javascript">
function doScript()
{
activeXTimerControl.Start();

activeXTimerControl.Stop();
var time = activeXTimerControl.Time;
activeXTimerControl.AddTimeToGrid("my timer", time);
activeXTimerControl.AddTimeToLabel("my timer", time);




}
</script>

</html>



August 14, 2008

AcitveX Controls - how to create one and call with Javascript

One way to get a .NET object to run in a browser is to create a User Control in .NET.
You can then call this User Control from Javascript in your client.

There's nothing magical about this except, it only works on IE and there are a few small bits that you must implement in order for your User Control to be picked up, the errors you get aren't very helpful so I recommend following these steps (Some of these I'm not sure if they're required but they've worked for me).

  1. Create a Class Project in Visual Studio 2005.
  2. Add a User Control to the project.
  3. In the User Control Designer add a TextBox Control
  4. Add a Property to your class to set the Text of the TextBox you've just added.
public void AddText(string time)
{
if (_timerTextBox == null)
{
_timerTextBox = new System.Windows.Forms.TextBox();
_timerTextBox.Text = " Time (msec)";
}
_timerTextBox.Text += ("\r\n" + time);
this.Refresh();
}
  1. Create an Interface in the same namespace as your UserControl and add the signature of the Property you've just added.
public interface IActiveXTimerControl
{
void AddText( string time);
}
  1. Mark the Class with a the GuidAttribute and also inherit it from the interface you've just created.
namespace ActiveXTimerControl
{
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("4F3B57CE-XXXX-XXXX-ACBD-EBB2380990E7")]
public partial class TimerControl : UserControl, IActiveXTimerControl
{
public TimerControl()
{
InitializeComponent();
}
}
}
  1. Mark the Assembly as Com Visible in the Project Properties "Assembly Information".
  2. Compile this and register it by adding a Post-Build Event to your project
  3. regasm $(TargetFileName) /tlb /codebase

You now have an Assembly which has a control usable in a browser.
Now to use this Control
Create a website and add the following aspx page (the content of this could also be in html page) inside the body




<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>

<OBJECT id="activeXTimerControl" name="activeXTimerControl" classid="clsid:4F3B57CE-D9CA-41c3-ACBD-EBB2380990E7" style="width: 504px; height: 182px" codebase="Bin/ActiveXTimerControl.dll" class="ActiveXTimerControl.TimerControl">
</OBJECT>

<form name="frm" id="frm">
<input type=button value="Click me" onClick="doScript();">
</form>


</body>

<script language="javascript">
function doScript()
{
activeXTimerControl.Start();
activeXTimerControl.Stop();
activeXTimerControl.AddText("time");

}
</script>

</html>



Note the clsid above this is crucial it must be the same GUID value as you gave the Class.

August 07, 2008

Localization and Globalization

Using Localization from Code

ASP.NET provides all the Localization you need by just adding .RESX files to your Web App in the right location but sometimes you want to invoke the Localization yourself. To do this you'll use the System.Resources.ResourceManager class. This allows you to get resources by simply supplying the name of the resource and the Culture but to setup the resource itself is a bit trickier (in Visual Studio 2005) as some of the files required have been removed from Visual Studio's Templates. Because of this you'll need to use some tools to create the Resource (and optionally the Assembly to store it know as Resource Assemblies or Satellite Assemblies).
There are 3 steps to creating resources for use with ResourceManager:
1. Write a text file or .RESX file to hold your resources.
2. Compile the resources in step 1 into a .RESOURCES file.
3. Compile the .RESOURCES file in step 2 into a Satellite Assembly.
4. Locate the Satellite Assembly from step 3 into a location which is recognised by the Localisation mechanism.

1. If your resources are all strings then the easiest thing to do is add them to a .TXT file. If your resources are more than strings e.g. images then you should probably use a .RESX file. .RESX files are the usual location for resources. These are added to your Class Library Project in VS by selecting the project and clicking the Add File and then choosing Resource File.
If you wish to use a .TXT file then here's how you enter the resources:
myFirstResource = here is my first resources value
mySecondResource = here is my second resources value
(Note that the .TXT file must be saved with UTF-8 support, you'll probably find this in your text editor save options somethere, notepad has it, this is required to support some culture specific characters like accents).

2. The .TXT file or .RESX file is not enough for the ResourceManager, this .RESX file has to be converted to a .RESOURCES file before it can be used, this can be done with the tool, ResGen.exe,
resgen strings.en.txt strings.resources
resgen strings.es.txt strings.es.resources.

for more see http://msdn.microsoft.com/en-us/library/xbx3z216(VS.80).aspx

If you wish to use these bare .RESOURCES files then you can by following this http://msdn.microsoft.com/en-us/library/khyt7e7y.aspx but note that there are issues with Concurrent access on IIS so you may want to embed the resource in an Assembly (next step).

3. This .RESOURCES file can then be embedded in your DLL for later use by the ResourceManager but I found some little caveats here which cost me at least 1 day to figure out. This caveat is that the naming of the .RESOURCES files is also used as well as the DLL.
What I found it that for other cultures the .RESOURCES file must be named with that Culture as it does get used e.g.
The Default culture is English (en), the other culture used is Spanish (es).

You create 2 .TXT files to store the resources
strings.txt and strings.es.txt.

You then generate 2 .RESOURCES files from these with, it is these files that must have the correct naming i.e. the Spanish version must have the ".es" in the name before the extension.
resgen strings.en.txt strings.resources
resgen strings.es.txt strings.es.resources.

Now you use a Linker tool call AL.exe to embed each resource into it's own dll, note that you supply the culture here at the command line and also you must name the DLLS for the Cultures other than the default to have the ".resources" before the extentsion:
al /embed:strings.resources /culture:en /out:strings.dll
al /embed:strings.es.resources /culture:es /out:strings.resources.dll

You now copy only the .dll into the correct directories i.e. if it's aWebsite the root directory is the "bin" directory and it it's a standalone app then just copy into the same dir as the .exe.
Place the default assembly. strings.dll into the root and them create a subdirectory "es" and place the Spanish DLL in there
e.g.
/bin/strings.dll
/bin/es/strings.resources.dll

for more see http://msdn.microsoft.com/en-us/library/21a15yht(VS.71).aspx

4. If you use the bare .RESOURCES file then you can use the method described here http://msdn.microsoft.com/en-us/library/khyt7e7y.aspx. All you need to do is name the files uniquely using the pattern described, e.g. strings.es.resources for Spanish. The use the ResourceManager _rm = ResourceManager.CreateFileBasedResourceManager("strings",Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Resources" ), null);
CultureInfo cInfo = new CultureInfo(culture);
string result = _rm.GetString(resourceStringName, cInfo);

If you use an Assembly to store your Resources then you cannot use the CreateFileBasedResourceManager instead you must use the ResourceManager's constructor.
Assembly stringsAssembly = Assembly.Load("strings");//the default assemblies name see step 3.
ResourceManager rm = new ResourceManager("strings", stringsAssembly );//"Strings" is the resource name of the default Culture i.e. strings.resource that you embedded earlier in the DLL.
string result = _rm.GetString("myFirstResource", new CultureInfo("es"));//get me the resource from the Spanish Culture DLL.