Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

NTime - Performance unit testing tool

, 30 Mar 2006
An article on a performance testing tool to test an application against its performance.
ntime_demo.zip
Installer
1.1
NTimeSetup.msi
Setup.Exe
Setup.Ini
2.0
NTimeSetup.msi
setup.exe
ntime_sdk.zip
ntime_src.zip
src
Framework
License.rtf
ntime.snk
NTimeConsole
App.ico
NTimeGUI
folder.bmp
folder2.bmp
gray.bmp
green.bmp
NTime.ico
red.bmp
yellow.bmp
NTimeSetup
NTimeSetup.vdproj
PerformanceTests
Readme.rtf
ntime_src_11.zip
NTime_src_1.1
Framework
.VPUML
.history
.historyinfo_NTime.Framework.dll
NTime.Framework.dll.vpp.v1
NTime.Framework.dll.vpp
bin
Release
NTime.Framework.dll
ModelSupport
default.txvpck
NTime
Framework
Framework.txvpck
NTime.txvpck
mssccprj.scc
NTime.Framework
NTime.Framework.dll.csproj.user
NTime.Framework.dll.csproj.vspscc
vssver.scc
ntime.snk
NTimeAddin
bin
Release
NTimeAddin.dll
NTimeAddin.tlb
NTimeAddin.csproj.user
NTimeAddinSetup
NTimeAddinSetup.vdproj
NTimeConsole
App.ico
bin
Release
NTime.exe
NTimeConsole.exe
ModelSupport
mssccprj.scc
NTime.csproj.user
NTime.csproj.vspscc
vssver.scc
NTimeGUI
.VPUML
.history
.historyinfo_NTimeGUI
NTimeGUI.vpp.v1
NTimeGUI.vpp
bin
Release
NTimeGUI.exe
folder.bmp
folder2.bmp
gray.bmp
green.bmp
ModelSupport
default.txvpck
NTime
GUI
GUI.txvpck
NTime.txvpck
mssccprj.scc
NTime.ico
NTimeGUI.csproj.user
NTimeGUI.csproj.vspscc
out
doc
doc-files
default.gif
gendoc.gdc
model-tree
false.gif
model.tree
ST_Attribute.gif
ST_Class Diagram.gif
ST_Class.gif
ST_Operation.gif
ST_Package Diagram.gif
ST_Package.gif
ST_Project.gif
true.gif
navigation.jar
NTime
doc-files
NTime.gif
GUI
doc-files
Class Diagram1.gif
GUI.gif
red.bmp
vssver.scc
yellow.bmp
NTimeSetup
mssccprj.scc
NTimeSetup.vdproj
NTimeSetup.vdproj.vspscc
vssver.scc
PerformanceTests
bin
Release
PerformanceTests.dll
PerformanceTests.ntime
ModelSupport
PerformanceTests
mssccprj.scc
PerformanceTests.csproj.user
PerformanceTests.csproj.vspscc
vssver.scc
ntime_src_20.zip
NTime_src_2.0
Framework
.VPUML
.history
.historyinfo_NTime.Framework.dll
NTime.Framework.dll.vpp.v1
NTime.Framework.dll.vpp
bin
Release
NTime.Framework.dll
ModelSupport
default.txvpck
NTime
Framework
Framework.txvpck
NTime.txvpck
mssccprj.scc
NTime.Framework
NTime.Framework.dll
NTime.Framework.dll.csproj.user
NTime.Framework.dll.csproj.vspscc
Properties
vssver.scc
NTimeConsole
App.ico
bin
Release
NTime.exe
NTime.Framework.dll
NTimeConsole.exe
ModelSupport
mssccprj.scc
NTime.csproj.user
NTime.csproj.vspscc
Properties
vssver.scc
NTimeGUI
.VPUML
.history
.historyinfo_NTimeGUI
NTimeGUI.vpp.v1
NTimeGUI.vpp
bin
Release
NTime.Framework.dll
NTimeGUI.exe
folder.bmp
folder2.bmp
gray.bmp
green.bmp
ModelSupport
default.txvpck
NTime
GUI
GUI.txvpck
NTime.txvpck
mssccprj.scc
NTime.ico
NTimeGUI.csproj.user
NTimeGUI.csproj.vspscc
out
doc
doc-files
default.gif
gendoc.gdc
model-tree
false.gif
model.tree
ST_Attribute.gif
ST_Class Diagram.gif
ST_Class.gif
ST_Operation.gif
ST_Package Diagram.gif
ST_Package.gif
ST_Project.gif
true.gif
navigation.jar
NTime
doc-files
NTime.gif
GUI
doc-files
Class Diagram1.gif
GUI.gif
Properties
red.bmp
vssver.scc
yellow.bmp
NTimeSetup
mssccprj.scc
NTimeSetup.vdproj
NTimeSetup.vdproj.vspscc
vssver.scc
PerformanceTests
bin
Release
NTime.Framework.dll
PerformanceTests.dll
PerformanceTests.ntime
ModelSupport
PerformanceTests
mssccprj.scc
PerformanceTests.csproj.user
PerformanceTests.csproj.vspscc
Properties
vssver.scc
#region Copyright (c) 2004, Adam Slosarski
/************************************************************************************
'
' Copyright  2004 Adam Slosarski
'
' This software is provided 'as-is', without any express or implied warranty. In no 
' event will the authors be held liable for any damages arising from the use of this 
' software.
' 
' Permission is granted to anyone to use this software for any purpose, including 
' commercial applications, and to alter it and redistribute it freely, subject to the 
' following restrictions:
'
' 1. The origin of this software must not be misrepresented; you must not claim that 
' you wrote the original software. If you use this software in a product, an 
' acknowledgment (see the following) in the product documentation is required.
'
' Portions Copyright  2004 Adam Slosarski
'
' 2. Altered source versions must be plainly marked as such, and must not be 
' misrepresented as being the original software.
'
' 3. This notice may not be removed or altered from any source distribution.
'
'***********************************************************************************/
#endregion
using System;
using System.Windows.Forms;
using System.Reflection;
using System.Collections;
using NTime.GUI;
using NTime.Framework;

namespace NTime
{
	/// <summary>
	/// This class performs common UI tasks invoked by user interaction.
	/// </summary>
	public class NTimeMain
	{
		/// <summary>
		/// Status info used in status bar.
		/// </summary>
		private string statusInfo;

		/// <summary>
		/// Total tests used in status bar.
		/// </summary>
		public int totalTests;

		/// <summary>
		/// Number of accepted tests used in status bar.
		/// </summary>
		public int acceptedTests;

		/// <summary>
		/// Number of rejected tests used in status bar.
		/// </summary>
		public int rejectedTests;

		/// <summary>
		/// Total test time used in status bar.
		/// </summary>
		public TimeSpan totalTestsTime;

		/// <summary>
		/// Proxy class to access application domain assemblies.
		/// </summary>
		public RemoteLoader rl;

		/// <summary>
		/// Application domain class to manage assemblies in isolated application domain.
		/// </summary>
		private AssemblyDomain Domain;

		/// <summary>
		/// Assembly filenames loaded into project. This list is used by <see cref="Reload"/> method to refresh assemblies with updated test classes and methods.
		/// </summary>
		public ArrayList AssemblyNames = new ArrayList();

		/// <summary>
		/// Main NTime form object to reference UI objects.
		/// </summary>
		public GUI.NTimeForm Form;

		/// <summary>
		/// Indicates whether project has changed.
		/// </summary>
		private bool dirtyProject = false;
		
		/// <summary>
		/// Indicates whether project name is default and need to be saved with different filename.
		/// </summary>
		private bool firstUse = true;
		
		/// <summary>
		/// Project filename.
		/// </summary>
		public string ProjectName;
		
		/// <summary>
		/// Watches changes in assemblies then refreshes them in project.
		/// </summary>
		private ArrayList directoryWatcher = new ArrayList();
		
		/// <summary>
		/// Indicating whether tests are running now.
		/// </summary>
		public bool Running;

		/// <summary>
		/// Configuration filename for application domain of loaded tests.
		/// </summary>
		public string ConfigurationFilename;

		/// <summary>
		/// Initializes a new instance of main underlayed class to perform common tasks.
		/// </summary>
		public NTimeMain()
		{
			Domain = new AssemblyDomain();
		}

		/// <summary>
		/// Refreshes caption form to show project name (project filename).
		/// </summary>
		private void UpdateCaption()
		{
			if(dirtyProject)
				Form.Text = System.IO.Path.GetFileNameWithoutExtension(ProjectName) + "* - NTime";
			else
				Form.Text = System.IO.Path.GetFileNameWithoutExtension(ProjectName) + " - NTime";
		}

		/// <summary>
		/// Updates status bar with performance test statistics
		/// </summary>
		private void UpdateStatusBar()
		{
			Form.StatusBarPanelInfo.Text = statusInfo;
			Form.StatusBarPanelTotal.Text = "Total Tests: " + totalTests.ToString();
			Form.StatusBarPanelAccepted.Text = "Accepted: " + acceptedTests.ToString();
			Form.StatusBarPanelRejected.Text = "Rejected: " + rejectedTests.ToString();
			Form.StatusBarPanelTime.Text = "Time: " + totalTestsTime.ToString();
		}
		/// <summary>
		/// Checks whether project has changed and needs to be saved.
		/// </summary>
		/// <returns>Returns true when new action is allowed to be done, otherwise must not change project state.</returns>
		private bool CheckDirty()
		{
			if(dirtyProject)
			{
				DialogResult dr = MessageBox.Show("The Project has changed.\n\nDo you want to save the changes?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
				if(dr == DialogResult.Yes)
				{
					Save();
					return dirtyProject == false;
				}
				else if(dr == DialogResult.No)
				{
					return true;
				}
				return false;
			}
			return true;
		}

		/// <summary>
		/// Checks whether the form may close. This method is used by Exit menu or Close form event.
		/// </summary>
		/// <returns></returns>
		public bool Close()
		{
			if(CheckDirty())
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// Creates new empty project.
		/// </summary>
		public void NewProject()
		{
			if(CheckDirty())
			{
				ProjectName = "Untitled.ntime";
				ConfigurationFilename = "";
				AssemblyNames.Clear();
				Reload();
				dirtyProject = false;
				firstUse = true;
				UpdateCaption();
			}
		}

		/// <summary>
		/// Opens dialog to specify filename to load project or assembly with custom tests.
		/// </summary>
		public void Open()
		{
			if(CheckDirty())
			{
				OpenFileDialog openFileDialog = new OpenFileDialog();
				openFileDialog.Filter = "NTime Projects & Assemblies (*.ntime,*.dll,*.exe)|*.ntime;*.dll;*.exe|All Files (*.*)|*.*";
				if(openFileDialog.ShowDialog() == DialogResult.OK)
				{
					string fileName = openFileDialog.FileName;
					if(System.IO.Path.GetExtension(fileName) == ".dll" || System.IO.Path.GetExtension(fileName) == ".exe")
					{
						dirtyProject = false;
						NewProject();
						firstUse = false;
						ProjectName = System.IO.Path.GetDirectoryName(fileName) + "\\" + System.IO.Path.GetFileNameWithoutExtension(fileName) + ".ntime";
						AssemblyNames.Add(fileName);
						ConfigurationFilename = System.IO.Path.GetFileName(fileName) + ".config";
						Reload();
					}
					else if(System.IO.Path.GetExtension(fileName) == ".ntime")
					{
						dirtyProject = false;
						NewProject();
						firstUse = false;
						ProjectName = fileName;
						LoadProject();
					}
					else
						MessageBox.Show("Unknown file format.\nOpen NTime project file or an assembly.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
		}

		/// <summary>
		/// Opens dialog to save project to chosen filename.
		/// </summary>
		public void SaveAs()
		{
			SaveFileDialog saveFileDialog = new SaveFileDialog();
			saveFileDialog.Filter = "NTime Projects (*.ntime)|*.ntime|All Files (*.*)|*.*";
			saveFileDialog.FileName = System.IO.Path.GetFileName(ProjectName);
			if(saveFileDialog.ShowDialog() == DialogResult.OK)
			{
				ProjectName = saveFileDialog.FileName;
				firstUse = false;
				Save();
				dirtyProject = false;
				UpdateCaption();
			}
		}

		/// <summary>
		/// Saves current project.
		/// </summary>
		/// <returns></returns>
		public bool Save()
		{
			if(firstUse)
				SaveAs();
			System.Xml.XmlTextWriter xmlwriter = new System.Xml.XmlTextWriter(ProjectName, System.Text.Encoding.UTF8);
			xmlwriter.Formatting = System.Xml.Formatting.Indented;
			xmlwriter.WriteStartDocument();
			xmlwriter.WriteStartElement("NTimeProject");

			xmlwriter.WriteStartElement("ConfigurationFilename");
			xmlwriter.WriteAttributeString("name", ConfigurationFilename);
			xmlwriter.WriteEndElement();
			
			xmlwriter.WriteStartElement("Assemblies");
			foreach(string s in AssemblyNames)
			{
				xmlwriter.WriteStartElement("Assembly");
				xmlwriter.WriteAttributeString("name", s);
				xmlwriter.WriteEndElement();
			}
			xmlwriter.WriteEndElement(); // Assemblies

			xmlwriter.WriteEndElement(); // NTimeProject
			xmlwriter.WriteEndDocument();
			xmlwriter.Close();

			dirtyProject = false;
			UpdateCaption();
			return true;
		}

		/// <summary>
		/// Loads and parses xml project file.
		/// </summary>
		public void LoadProject()
		{
			System.Xml.XmlTextReader xmlreader = new System.Xml.XmlTextReader(ProjectName);
			xmlreader.MoveToContent();
			while(xmlreader.Read())
			{
				if(xmlreader.NodeType == System.Xml.XmlNodeType.Element)
				{
					switch(xmlreader.Name)
					{
						case "Assembly":
							string assemblyName = xmlreader.GetAttribute("name");
                            AssemblyNames.Add(assemblyName);
							break;
						case "ConfigurationFilename":
							ConfigurationFilename = xmlreader.GetAttribute("name");
							break;
					}
				}
			}
			Reload();
		}

		/// <summary>
		/// Runs loaded and selected tests.
		/// </summary>
		public void Run()
		{
			Reload();
			Running = true;
			ResetNodesArray(Form.TreeView.Nodes[0]);
			Form.toolBarRun.Enabled = false;
			Form.menuRun.Enabled = false;
			Form.toolBarStop.Enabled = true;
			Form.menuStop.Enabled = true;
			Form.toolBarNewProject.Enabled = false;
			Form.menuNewProject.Enabled = false;
			Form.toolBarOpenProject.Enabled = false;
			Form.menuOpen.Enabled = false;
			Form.menuAddAssembly.Enabled = false;
			Form.menuReload.Enabled = false;
			Form.menuRecent.Enabled = false;
			acceptedTests = 0;
			rejectedTests = 0;
			totalTestsTime = TimeSpan.Zero;
			statusInfo = "Running";
			UpdateStatusBar();
			ArrayList a = new ArrayList();
			if(Form.TreeView.SelectedNode != null)
			{
				BuildNodesArray(Form.TreeView.SelectedNode, ref a);
				if(a.Count > 0)
				{
					rl.RunUnit(a);
					Form.TimerQuery.Enabled = true;
				}
			}
		}

		/// <summary>
		/// Queries for finished tests to update treenodes and gui.
		/// This method is executed in specified time periods.
		/// </summary>
		public void QueryTests()
		{
			TestUnit tu;
			do
			{
				tu = rl.QueryUnit(false);
				if(tu != null)
				{
					if(tu.Result == UnitResult.Accepted)
					{
						tu.Node.ImageIndex = 1;
						tu.Node.SelectedImageIndex = 1;
						acceptedTests++;
					}
					else if(tu.Result == UnitResult.Rejected)
					{
						tu.Node.ImageIndex = 2;
						tu.Node.SelectedImageIndex = 2;
						rejectedTests++;
					}
					else if(tu.Result == UnitResult.Ignored)
					{
						tu.Node.ImageIndex = 3;
						tu.Node.SelectedImageIndex = 3;
					}
					UpdateStatusBar();
				}
				else
				{
					lock(rl)
					{
						if(rl.TestsFinished)
						{
							totalTestsTime = rl.TotalTime;
							UpdateStatusBar();
							Form.TimerQuery.Enabled = false;
							Running = false;
							Form.toolBarRun.Enabled = true;
							Form.menuRun.Enabled = true;
							Form.toolBarStop.Enabled = false;
							Form.menuStop.Enabled = false;
							Form.toolBarNewProject.Enabled = true;
							Form.menuNewProject.Enabled = true;
							Form.toolBarOpenProject.Enabled = true;
							Form.menuOpen.Enabled = true;
							Form.menuAddAssembly.Enabled = true;
							Form.menuReload.Enabled = true;
							// TODO: gdy bedzie gotowy recent menu to tutaj robic true
//							Form.menuRecent.Enabled = true;

							statusInfo = "Ready";
							UpdateStatusBar();
							if(Form.TreeView.SelectedNode != null)
							{
								ShowNodeInfo();
							}
							break;
						}
					}
				}
			} while(tu != null);
		}

		/// <summary>
		/// Fills recursive an array with selected tests in TreeView.
		/// </summary>
		/// <param name="t">Start node.</param>
		/// <param name="array">Array to fill in.</param>
		private void BuildNodesArray(TreeNode t, ref ArrayList array)
		{
			if(t.Tag != null)
			{
				((TestUnit)t.Tag).Node = t;
				array.Add(t.Tag);
			}
			foreach(TreeNode t2 in t.Nodes)
			{
				BuildNodesArray(t2, ref array);
			}
		}

		/// <summary>
		/// Resets tree node icon to default state. Used when starting tests.
		/// </summary>
		/// <param name="t">Start node.</param>
		private void ResetNodesArray(TreeNode t)
		{
			if(t.Nodes.Count == 0)
			{
				t.ImageIndex = 0;
				t.SelectedImageIndex = 0;
			}
			else
			{
				t.ImageIndex = 4;
				t.SelectedImageIndex = 4;
			}
			foreach(TreeNode t2 in t.Nodes)
			{
				ResetNodesArray(t2);
			}
		}

		/// <summary>
		/// Stops running tests.
		/// </summary>
		public void Stop()
		{
			if(Running)
			{
				statusInfo = "Stopping";
				UpdateStatusBar();
				Form.toolBarRun.Enabled = false;
				Form.menuRun.Enabled = false;
				Form.toolBarStop.Enabled = false;
				Form.menuStop.Enabled = false;
				rl.QueryUnit(true);
			}
		}


		/// <summary>
		/// Opens options dialog box.
		/// </summary>
		public void Options()
		{
			OptionsDialog optionsDialog = new OptionsDialog();
			optionsDialog.ConfigFilename.Text = ConfigurationFilename;
			if(DialogResult.OK == optionsDialog.ShowDialog())
			{
				ConfigurationFilename = optionsDialog.ConfigFilename.Text;
				dirtyProject = true;
				UpdateCaption();
				Reload();
			}
		}

		/// <summary>
		/// Places summary test text in edit box right to TreeView.
		/// </summary>
		public void ShowNodeInfo()
		{
			Form.NodeInfo.Text = "";
			ArrayList array = new ArrayList();
			BuildNodesArray(Form.TreeView.SelectedNode, ref array);
			ArrayList desc = new ArrayList();

			foreach(TestUnit tu in array)
			{
				desc.InsertRange(desc.Count, tu.UnitResultInfo);
			}
			if(desc.Count > 0)
				Form.NodeInfo.Lines = (string[])desc.ToArray(typeof(string));
		}

		/// <summary>
		/// Reloads assemblies. This function is used often for refreshing program state.
		/// </summary>
		public void Reload()
		{
			statusInfo = "Loading";
			UpdateStatusBar();
			lock(this)
			{
				if(rl != null)
					Domain.DestroyDomain();
				string[] st = (string[])AssemblyNames.ToArray(typeof(string));
				rl = Domain.CreateDomain(st, ConfigurationFilename);

				UpdateCaption();
				Form.TreeView.Nodes.Clear();
				TreeNode n = Form.TreeView.Nodes.Add("Performance Tests");
				n.ImageIndex = 4;
				n.SelectedImageIndex = 4;
				Form.menuReload.Enabled = AssemblyNames.Count > 0;

				foreach(string s in AssemblyNames)
				{
					rl.LoadAssembly(s);
				}
				PopulateTree();

				directoryWatcher.Clear();
				for(int i = 0; i < AssemblyNames.Count; i++)
				{
					directoryWatcher.Add(new System.IO.FileSystemWatcher(System.IO.Path.GetDirectoryName(AssemblyNames[i].ToString()), "*.*"));
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Created += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Changed += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).Deleted += new System.IO.FileSystemEventHandler(onFileChange);
					((System.IO.FileSystemWatcher)directoryWatcher[i]).EnableRaisingEvents = true;
				}
				statusInfo = "Ready";
				UpdateStatusBar();
				Form.TreeView.SelectedNode = Form.TreeView.Nodes[0];
				//			Domain.DestroyDomain();
			}
		}

		/// <summary>
		/// Fired when file watcher finds any change in directory.
		/// </summary>
		/// <param name="obj">File watcher object.</param>
		/// <param name="e">File watcher event arguments.</param>
		private void onFileChange(object obj, System.IO.FileSystemEventArgs e)
		{
			foreach(string s in AssemblyNames)
			{
				if(s.ToLower() == e.FullPath.ToLower())
				{
					if(Running == false)
						Form.TimerFile.Enabled = true;
					break;
				}
			}
		}

		/// <summary>
		/// Invokes assembly editor to add or remove assemblies with tests.
		/// </summary>
		public void AddAssembly()
		{
			AddAssemblyForm form = new AddAssemblyForm();
			form.listBox.Items.Clear();
			foreach(string s in AssemblyNames)
			{
				form.listBox.Items.Add(s);
			}
			if(form.ShowDialog() == DialogResult.OK)
			{
				dirtyProject = true;
				AssemblyNames.Clear();
				foreach(string s in form.listBox.Items)
				{
					AssemblyNames.Add(s);
				}
				UpdateCaption();
				Reload();
			}
		}

		/// <summary>
		/// Populates tree with loaded tests in assemblies.
		/// </summary>
		private void PopulateTree()
		{
			Form.menuExpand.Enabled = false;
			Form.menuExpandAll.Enabled = false;
			Form.menuCollapse.Enabled = false;
			Form.menuCollapseAll.Enabled = false;
			Form.menuRun.Enabled = false;
			Form.toolBarRun.Enabled = false;
			totalTests = 0;
			totalTestsTime = TimeSpan.Zero;
			acceptedTests = 0;
			rejectedTests = 0;
			if(rl != null)
			{
				foreach(TestFixture tf in rl.Fixtures)
				{
					foreach(TestUnit tu in tf.Units)
					{
						AddToTree(tu.ObjectName.ToString(), tu);
						totalTests++;
					}
				}
			}
			Form.TreeView.ExpandAll();
		}

		/// <summary>
		/// Adds node to treeview control.
		/// </summary>
		/// <param name="typeName">.net type (with namespace) to add to treeview.</param>
		/// <param name="testUnit">TestUnit instance to add to node tag for its further reference.</param>
		private void AddToTree(string typeName, TestUnit testUnit)
		{
			string[] ns = typeName.Split('.', '+');
			TreeNode n = Form.TreeView.Nodes[0];
			foreach(string s in ns)
			{
				bool found = false;
				foreach(TreeNode t in n.Nodes)
				{
					if(t.Text == s)
					{
						found = true;
						n = t;
						break;
					}
				}
				if(found == false)
				{
					if(s == ns[ns.Length - 1])
						n = n.Nodes.Add(s + " (" + testUnit.TypeName + ")");
					else
						n = n.Nodes.Add(s);
					n.ImageIndex = 4;
					n.SelectedImageIndex = 4;
					if(s == ns[ns.Length - 1]) // TODO: tu jest chyba blad gdy klasa i metoda maja te same nazwy
					{
						n.Tag = testUnit;
						n.ImageIndex = 0;
						n.SelectedImageIndex = 0;
					}
				}
			}
			Form.menuExpand.Enabled = true;
			Form.menuExpandAll.Enabled = true;
			Form.menuCollapse.Enabled = true;
			Form.menuCollapseAll.Enabled = true;
			Form.menuRun.Enabled = true;
			Form.toolBarRun.Enabled = true;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

AdamSlosarski
Web Developer
Poland Poland
Born in Poland, living there as employeed developer, in free time writing much .net stuff and designing applications.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 31 Mar 2006
Article Copyright 2004 by AdamSlosarski
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid