Monday, February 16, 2009

A small functionality change.

Here's what can happen when a small functionality change is required in a fairly complex application. In this case, there are a few TabPages that are generated dynamically. Some of the TabPages have a 'Save Details' button.

The change: one of the TabPages now should NOT have a 'Save Details' button if the object is a 'new' object (not yet saved in the database). Easy!

Solution:

this.tabControl.Controls.Add(this.mySomethingTabPage);

HideOrShowSaveButton(this.mySomethingTabPage); // solution

...

public void HideOrShowSaveButton(TabPage tabPage)
{
//'save details' should not appear on the 'MySomething' tab page if
//the object is a new object

//find 'save details' button

ToolStripButton mySmallSaveButton = null;

foreach (Control c in this.mySomethingTabPage.Controls)
{
SomeDetails.SomeDetailsHeadingView pdhv = c as SomeDetails.SomeDetailsHeadingView;

if (pdhv != null)
{
foreach (Control c1 in pdhv.Controls)
{
ToolStrip headingToolStrip = c1 as ToolStrip;

if (headingToolStrip != null)
{
foreach (ToolStripItem item in headingToolStrip.Items)
{
if (item.Name == "SaveButton")
{
mySmallSaveButton = item as ToolStripButton;

//hide button on the 'someDetails' tab if new object
if (mySmallSaveButton != null)
{
if (Global.currentObject.IsNewObject)
{
mySmallSaveButton.Visible = false;
mySmallSaveButton.Enabled = false;
}
else
{
mySmallSaveButton.Visible = true;
mySmallSaveButton.Enabled = true;
}
return;
}
}
}
}
}
}
}
}

Had to hardcode the 'SaveButton' name which I would really want to avoid, but did not want to spend more time on that.

Thoughts: this application could have been designed a little better to make future changes not so painful.

by . Also posted on my website

Thursday, February 12, 2009

Asynchronous calls to a WebService

Figured out how to call a WebService asynchronously using a Callback in .NET. The process is fairly easy and straightforward.

This article Professional ASP.NET Web Services : Asynchronous Programming provided me with most of the information that I needed, and even had a few solutions discussed, of which I chose the one which suits me best

First of all, of course, I need a WebService

namespace AsyncService
{
///
/// Summary description for Service1
///

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX,
// uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class AsyncService : System.Web.Services.WebService
{

[WebMethod(Description="Returna a random value under 1000")]
public int[] GetRandomValue(int id, int delay)
{
Random random = new Random();
int randomValue = random.Next(1000);
int[] returnValue = new int[] { id, randomValue };
Thread.Sleep(delay);
return returnValue;
}
}
}

This service only returns a random number. It also returns the number after some delay to imitate that it is actually does something useful.

A Web Service proxy class provides a wrapper that lets me communicate to a WebService.

namespace AsyncCaller
{
[WebServiceBindingAttribute(Name = "AsyncRequestSoap", Namespace
= "http://tempuri.org/")]
public class AsyncCallerProxy : SoapHttpClientProtocol
{
public AsyncCallerProxy()
{
this.Url = "http://localhost/MyAsyncService/AsyncService.asmx";
}

[SoapDocumentMethodAttribute("http://tempuri.org/GetRandomValue",
Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
public int[] GetRandomValue(int id, int delay)
{
object[] results = this.Invoke("GetRandomValue", new object[]
{ id, delay });
return ((int[])results[0]);
}

public IAsyncResult BeginGetRandomValue(int id, int delay,
AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("GetRandomValue", new object[] {
id, delay}, callback, asyncState);
}

public int[] EndGetRandomValue(IAsyncResult asyncResult)
{
object[] results = this.EndInvoke(asyncResult);
return ((int[])(results[0]));
}
}
}

Now I want to make a small demonstration of asynchronous communication to the WebService.

Before that I would need a very simple helper class to make it easy.

public class AsyncHelper
{
public AsyncHelper(int id)
{
this.HelperID = id;
Random random = new Random();
this.RandomDelay = random.Next(10000);
}

int _helperID = 0;

public int HelperID
{
get { return _helperID; }
set { _helperID = value; }
}

int _randomDelay = 0;

public int RandomDelay
{
get { return _randomDelay; }
set { _randomDelay = value; }
}

int _randomResult = 0;

public int RandomResult
{
get { return _randomResult; }
set { _randomResult = value; }
}
}

When an instatnce of the class is created, it is assigned a random delay value. I will pass it to the WebService and will get a response after a number of milliseconds defined by RandomDelay value.

On my demo application form I have a button and two DataGridViews.

When a button is pressed, an instance of the AsyncHelper class is created and added to the list of currently running requests, which is bound to the first DataGridView. The ID of the class instance and the delay value are passed to the WebService.

After the delay, the WebService returns the ID and the random ‘Result’. An instance of the AsyncHelper is found by ID, the result is assigned and the instance is removed from the current requests list and added to the processed requests list. If the button is pressed multiple times, a user can see multiple requests being added to the list and being returned by the WebService after the delays specified.

public partial class Form1 : Form
{
AsyncCallerProxy objWebService = new AsyncCallerProxy();

//counter for number of requests sent
private int _requestCounter = 0;
private List _asyncRequests = new List();
private List _asyncRequestsProcessed =
new List();

public Form1()
{
InitializeComponent();
bindingSourceRequests.DataSource = _asyncRequests;
dataGridViewRequests.DataSource = bindingSourceRequests;

bindingSourceRequestsProcessed.DataSource = _asyncRequestsProcessed;
dataGridViewRequestsProcessed.DataSource =
bindingSourceRequestsProcessed;

dataGridViewRequests.Columns[0].DataPropertyName = "HelperID";
dataGridViewRequests.Columns[1].DataPropertyName = "RandomDelay";

dataGridViewRequestsProcessed.Columns[0].DataPropertyName =
"HelperID";
dataGridViewRequestsProcessed.Columns[1].DataPropertyName =
"RandomDelay";
dataGridViewRequestsProcessed.Columns[2].DataPropertyName =
"RandomResult";
}

private void buttonRequest_Click(object sender, EventArgs e)
{
//create a new request and add it to request queue
AsyncHelper newRequest = new AsyncHelper(_requestCounter);
_requestCounter++;

_asyncRequests.Add(newRequest);

AsyncCallback asyncCallback = new AsyncCallback(MyCallBack);

IAsyncResult asyncResult;

asyncResult = objWebService.BeginGetRandomValue
(newRequest.HelperID, newRequest.RandomDelay, asyncCallback, null);

UpdateRequestQueueDisplay();
}

private void MyCallBack(IAsyncResult asyncResult)
{
int[] returnValue = objWebService.EndGetRandomValue(asyncResult);
int id = returnValue[0];
int result = returnValue[1];

AsyncHelper currentRequest =
_asyncRequests.Find(delegate(AsyncHelper testRequest)
{return testRequest.HelperID == id;});

//request is processed, remove it from the queue and add to processed
//requests list
if (currentRequest != null)
{
currentRequest.RandomResult = result;
_asyncRequestsProcessed.Add(currentRequest);
_asyncRequests.Remove(currentRequest);

UpdateRequestQueueDisplay();
}
}

private void UpdateRequestQueueDisplay()
{
//fixes the cross-thread issue while accessing the form controls
this.BeginInvoke(new MethodInvoker(delegate()
{
this.bindingSourceRequests.ResetBindings(false);
this.bindingSourceRequestsProcessed.ResetBindings(false);
}));

}
}
by . Also posted on my website

Wednesday, February 11, 2009

Top-level exception handling

Found a good article on exception handling. Top-level Exception Handling in Windows Forms Applications and followed some advice from it.

What it meant for me, basically, is that I changed the Program.cs file of my application from this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace MyNameSpace
{
static class Program
{
///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormUpdater());
}
}
}

to this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace MyNameSpace
{
static class Program
{
///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{
Application.ThreadException +=
new ThreadExceptionEventHandler(new
ThreadExceptionHandler().ApplicationThreadException);

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FormUpdater());
}

public class ThreadExceptionHandler
{
public void ApplicationThreadException
(object sender, ThreadExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}

and that allowed me to get rid of a couple of dozens try/catch blocks in the application code without losing any exception handling functionality. Quite handy.

by . Also posted on my website

Tuesday, February 10, 2009

Scanner Update

I tried the solution suggested in this thread. HOW TO RETRIEVE INSTALLED SCANNER

I did the following:

  • Downloaded wiaaut.dll

  • Copied it to system32

  • Registered it with "regsvr32 wiaaut.dll" (successfully)

  • Added a reference to wiaaut.dll to my project in Visual Studio.NET

  • Checked that the Windows Image Acquisition (WIA) service is running
  • Next, I added and debugged the following code:

    WIA.DeviceManager manager = new WIA.DeviceManagerClass();
    WIA.DeviceManagerClass managerClass = new WIA.DeviceManagerClass();

    string wdeviceName = "";
    foreach (WIA.DeviceInfo info in manager.DeviceInfos)
    {
    if (info.Type == WIA.WiaDeviceType.ScannerDeviceType)
    {
    foreach (WIA.Property p in info.Properties)
    {
    if (p.Name == "Name")
    {
    wdeviceName = ((WIA.IProperty)p).get_Value().ToString();
    Console.WriteLine(wdeviceName);
    }
    }
    }
    }

    However, the manager.DeviceInfos is always empty. I have 2 scanners attached, one of them shows in Control Panel->Scanners and Cameras, one doesn't, and both show under "Imaging Devices" in Device manager.

    At this point, the only idea I have is that the scanner drivers just do not support WIA. As long as I have no way to check if this is true or not, I'll have to stick to the yesterday's solution.

    by . Also posted on my website

    Monday, February 9, 2009

    Finding a scanner

    I need to find out what scanners are attached to the computer. I also need to give the user of my application an option to select the default scanner, and to change this selection at any time. Fortunately, the scanner can only be one of a few models. Therefore, this is the solution I came up with so far:

    ArrayList scanners = new ArrayList();

    ManagementObjectSearcher search = new System.Management.ManagementObjectSearcher
    ("SELECT * From Win32_PnPEntity");

    ManagementObjectCollection deviceCollection = search.Get();

    foreach (ManagementObject info in deviceCollection)
    {
    string deviceName = Convert.ToString(info["Caption"]);

    if (/* check deviceName for certain substrings */)
    {
    scanners.Add(deviceName);
    }
    }

    However, there are at least two things that can be improved, though I don't know if they're possible.

    First, I would like to get only those devices that are under "Imaging devices" in Device Manager. That would be a huge improvement as I currently have almost 200 entries in the deviceCollection, and only 2 of them are under "Imaging Devices".

    Also, I would like to find a way to check if the device is a scanner. That would help to provide a "general" solution where the scanner attached may be of any model.

    by . Also posted on my website

    Thursday, February 5, 2009

    Wrote a Windows Service

    Wrote my first windows service today. Was not hard at all, I mostly followed the guide

    Simple Windows Service Sample

    However, the way the service in the sample is logging events was not suitable for me. I needed to log the events into the Application log of the Event Viewer. I also needed to catch exceptions and log them as errors in the Application log. So I used the powers of Enterprise Library event logging and exception handling. Firstly, I added references to EnterpriseLibrary and the corresponding 'usings' to the windows service


    using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
    using Microsoft.Practices.EnterpriseLibrary.Logging;

    Then, I added the loggingConfiguration and exceptionHandling sections to the appConfig file, that looked like this:


    <configSections>
    <section name="loggingConfiguration"
    type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings,
    Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=null" />
    <section name="exceptionHandling" type=
    "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.
    ExceptionHandlingSettings,
    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
    Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </configSections>

    ...

    <loggingConfiguration name="Logging Application Block" tracingEnabled="true"
    defaultCategory="" logWarningsWhenNoCategoriesMatch="true">
    <listeners>
    <add source=" " formatter="Text Formatter" log="Application"
    machineName="" listenerDataType=
    "Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.
    FormattedEventLogTraceListenerData,
    Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=null"
    traceOutputOptions="None" type="Microsoft.Practices.EnterpriseLibrary.Logging.
    TraceListeners.FormattedEventLogTraceListener,
    Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null"
    name="MyService EventLog TraceListener" />
    </listeners>
    <formatters>
    <add template="Timestamp: {timestamp} Message: {message} Category: {category} "
    type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter,
    Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=null" name="Text Formatter" />
    </formatters>
    <categorySources>
    <add switchValue="All" name="MyService">
    <listeners>
    <add name="MyService EventLog TraceListener" />
    </listeners>
    </add>
    </categorySources>
    <specialSources>
    <allEvents switchValue="All" name="All Events" />
    <notProcessed switchValue="All" name="Unprocessed Category" />
    <errors switchValue="All" name="Logging Errors & Warnings">
    <listeners>
    <add name="MyService EventLog TraceListener" />
    </listeners>
    </errors>
    </specialSources>
    </loggingConfiguration>

    <exceptionHandling>
    <exceptionPolicies>
    <add name="PagingPolicy">
    <exceptionTypes>
    <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    postHandlingAction="None" name="Exception">
    <exceptionHandlers>
    <add logCategory="MyService" eventId="100" severity="Error"
    title="MyService Exception Handling" formatterType=
    "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter,
    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=null" priority="0" type=
    "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler,
    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging,
    Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=null"
    name="MyService Logging Handler" />
    </exceptionHandlers>
    </add>
    </exceptionTypes>
    </add>
    </exceptionPolicies>
    </exceptionHandling>

    I'll be honest, I would not be able to explain every single line in this XML snippet. I just know that it works that way, and when I try removing some parts of it which seem to be unnecessary to me, the whole application usually starts failing.

    Next, I put the following into the OnStart method of the service:


    try
    {
    Logger.Write("MyService Process Started: " + getNow(), "MyService");
    timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
    timer.Interval = 10000;
    timer.Enabled = true;
    }
    catch (Exception ex)
    {
    ExceptionPolicy.HandleException(ex, "MyService");
    errorCount++;
    }

    and into the OnStop method


    Logger.Write("MyService Process Stopped: " + getNow(), "MyService");
    timer.Enabled = false;

    and into the timer_Elapsed method


    Logger.Write("running MyService process at " + getNow(), "MyService");
    RunMainFunction();

    (getNow() simply returns DateTime stamp in the required format)

    and I started getting events written into the Application log.

    Actually, logging events into the event log seems to be faster and easier than trying to debug the service. At least with my little service, where the whole cycle of stopping service -> uninstallation -> building a service + installation package -> installation -> starting a new version of a service can be done in under one minute.

    by . Also posted on my website

    Wednesday, February 4, 2009

    Team Foundation Server 2008 Adventures

    I’m playing with the Team Foundation Server 2008 these days. After I mastered the installation process, time has come to play around with security settings, group memberships and things like that.The first real issue I came across was having a problem with adding a Windows Group to TFS Licensed Users group. This is done through Team->Team Foundation Server Settings -> Group Memberships. The error message I got was very uninspiring and unhelpful.

    At the same time I could add single users from the same group without any problems. So my next step was to try to add every user from the group individually one by one. This way, after adding five users, I got the more helpful error message

    Aha! There is an error code this time. Now I can find out why. Turns out that the 5 user limitation is specific to the TFS – Workgroup edition. Ok, I do not even know for sure what is the edition I have installed and it does not say on the Help->About.

    This page comes very handy

    Which Version of Team Foundation Server Do I Have?

    So, I check the value of the registry key under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\TeamFoundation\Registration\Edition_CT And it is ‘Workgroup’ indeed. Confirmed!

    Now I need to upgrade to the Standard Edition. I have the key, but when I go to Add and Remove programs, run Change/Remove and select the option to Upgrade, the boxes where I’m supposed to enter the license key are grayed out! I’m stuck!

    Luckily, I find some help.

    Upgrading TFS 2008 Workgroup to Std. Edition

    Here is what the wise guys from Microsoft advise:

    1. Find a Setup.sdb file at the folder

    \Microsoft Visual Studio 2008 Team Foundation Server\Microsoft Visual Studio 2008 Team Foundation Server - ENU\

    2. Open the Setup.sdb file with text editor; you can back up the file before editing it.

    3. Remove the “[Product Key]” line and the PID line after it in the file.

    Try to upgrade again.

    And this works. I re-check the registry key value and, indeed, it has changed to “Full” which means Standard Edition. Hooray! However, when I try to add a 6th user to the TFS Licensed users, I still come up with the same error. Now I consider reinstalling from scratch! Next, I come across this discussion.

    Upgrading from limited version

    And the last bit of advice seems to be my case

    “If you are using the full RTM version (not the workgroup version) do not use the Licensed Users Group. It is not used by the full version but is still limited to 5 users. Just add the users to the project groups.”

    And it works too. I can add Windows Users and Groups to project groups.

    Finally, a few words about deleting projects.
    A simple way to delete the project forever is explained here

    Team System Delete Project

    “If you need to delete a Team System Project you need to do it through a command line utility that is installed with Team Explorer. There is no way to delete a project from Team System except through the command line tool, TFSDeleteProject.exe. This utility is in the c:\program files\Microsoft Visual Studio 8\Common7\IDE\ folder by default. To delete a project, use the following syntax:

    TFSDeleteProject /server:ServerName ProjectName”

    However, if you are getting the TF30063 error, it might be worth looking at this post, it worked like a charm in my situation:

    TF30063: You are not authorized to access...

    “Ever try to delete a team project and get the above message? Are you in the Team Foundation Administrators group and scratching your head?”

    by . Also posted on my website