jump to navigation

Late binding DataGridView data with BackgroundWorker threads October 18, 2010

Posted by codinglifestyle in C#, Parallelism, Uncategorized, Winform.
Tags: , , ,
1 comment so far

In an increasingly multicore world you may notice your WinForm app never pushes your machine to 100%.  Great, right?  Erm, no.  Winforms are traditionally single threaded applications meaning we typically only tap into 1/2, 1/4, or even 1/8th of our processor’s potential.

I’ve recently been working on a utility containing a DataGridView for displaying work item data from TFS. Some of the column data had to be calculated so when displaying 500+ records the whole app was slowing down considerably. What I wanted was a delayed binding such that the cell would be initially blank, launch a thread, and update itself when the thread returned with the value. It turned out this was pretty easy.

First, use a data entity. You probably don’t have to, but I find having this layer offers an obvious place to add the changes I’ll cover below. The ideal place is in the bound property’s getter. Here you can see that the value is not yet calculated, launch a thread, and return blank or another default value in the meantime.

private int _nWeight = -1;

public int Weight

{

get

{

if (_nWeight < 0)

{

Tfs.GetWeight(Tag, GetWeightComplete);

_nWeight = 0;

}

return _nWeight;

}

}

private void GetWeightComplete(object sender, RunWorkerCompletedEventArgs e)

{

_nWeight = (int)e.Result;

if (Row < FormMain.Instance.Grid.Rows.Count)

FormMain.Instance.Grid.InvalidateRow(Row);

}

The property above represents the weight of the entity to be used in sorting or graphing by importance. Calculating the weight is a time intensive operation and is a great candidate for calculating in a worker thread. Notice a backing store, _nWeight, is used to check if the value has been calculated and also to cache the value. If _nWeight is uncached (-1) we launch the thread and return a default weight of 0 while the thread calculates the actual value. Notice when we call Tfs.GetWeight we pass the delegate, GetWeightComplete, as an argument. This function will ultimately be called when the thread returns.

public static void GetWeight(WorkItem wi, RunWorkerCompletedEventHandler onCompleteEvent)

{

BackgroundWorker worker    = new BackgroundWorker();

worker.DoWork             += new DoWorkEventHandler(GetWeightDoWork);

worker.RunWorkerCompleted += onCompleteEvent;

worker.RunWorkerAsync(wi);

}

private static void GetWeightDoWork(object sender, DoWorkEventArgs e)

{

WorkItem wi = (WorkItem)e.Argument;

int result = 0;

foreach (Revision rev in wi.Revisions)

{

if (rev.Fields[CoreField.ChangedBy].Value.ToString() == wi.Store.TeamFoundationServer.AuthenticatedUserDisplayName)

result += 1;

}

result = Math.Min(result, 10);

e.Result = result;

}

When a call is made to GetWeight you can see it uses the standard System.CompnentModel.BackgroundWorker class to manage the work. This has two main advantages: 1) an easy to use asynchronous event based pattern and 2) uses the thread pool for optimal thread management. Notice the two events, DoWork and RunWorkerCompleted are set before the thread is launched and that we can pass an arguement via RunWorkerAsync. GetWeightDoWork is called when the thread is launched and sets the result to the event arguments.  When we leave this function the RunWorkerCompleted event is called.

Finally, back in the data entity, GetWeightComplete is called when the thread has calculated the value.  The result is taken from the RunWorkercompletedEventArgs and set to the backing store. The form uses the singleton pattern and exposes the grid as a property (see here). This allows the data entity to easily invalidate the row which redraws the row taking the Weight value into account (in my case the weight determined the intensity of a yellow highlight drawn over the row indicating how often the authenticated user appeared in the work item’s revision history).

The user experience when using the above method is truely fantastic.  You get a responsive UI which immediately displays information with calculated information quickly coming in a few seconds later.  The standard binding method of the DataGridView further enhances this experience by only binding the data currently shown to the user.  So if only the first 25 rows are displayed, only those values will be calculated.  As we scroll down to show more rows the DataGridView will calculate only the newly bound rows (saving loads of time for potentially 1000’s of rows never shown).  Good luck unlocking your other cores for a better UI experience.

Advertisement

Start a WinForm Hidden June 18, 2009

Posted by codinglifestyle in CodeProject, Winform.
Tags: , , , ,
add a comment
This is a short one, there may be better ways but this was fast and simple.  Works for me!
The case here is I have a WinForm application and want the Main window to start hidden.  From the designer, you can’t set Visisble = false.  Also, setting this.Visible=false in the constructor or Load event has no effect.  Sure you can set it later (like in Paint) but the last thing you want is your window to flash and disappear every time your application starts.
So, here is all you have to do:
  1. In the designer, set your window Opacity to 0%
  2. In the constructor, pass in a boolean to indicate if the window is displayed.
  3. In the function body, add this line:
    Opacity = bVisible ? 100 : 0;
Simple, fast, done… moving on to other things in life.

Well okay… I’m not really one for leaving well enough alone.  The fact is, there is a better way but you’re not going to like it!  I imagine your main form is doing something like sitting in the notification tray and/or monitoring something like a windows service.  That functionality, whatever it is your application does, should be abstracted out of the form altogether.  You’re really not going to like what’s next.  This object, the meat of your program, can then exist in a worker thread and provide the relevant events to your UI so it may update itself if the main form is shown.
It’s not as bad as it sounds.  Go on, you know separating the logic from the UI layer is the right design!  We’ll abstract all our real functionality into a class called MyFunctionality.  We have BackgroundWorker in our .NET toolbox MyFunctionality will inherit from.  Then override OnDoWork where we’ll put the code we originally had in the main form’s constructor or Load event.  We will handle the case of exiting the thread in case the user logs off.  Last, we just need optional events for the UI to receive an update from our functionality object.  An example of this is below, with our DisplayChanged event and DoDisplayChanged function.  Use a custom event to pass more information.
class MyFunctionality : System.ComponentModel.BackgroundWorker
{
public event EventHandler DisplayChanged = null;
protected override void OnDoWork(DoWorkEventArgs e)
{
//The meat of my program…
Microsoft.Win32.SystemEvents.SessionEnded += new Microsoft.Win32.SessionEndedEventHandler(SystemEvents_SessionEnded);
base.OnDoWork(e);
}
void SystemEvents_SessionEnded(object sender, Microsoft.Win32.SessionEndedEventArgs e)
{
this.CancelAsync();
}
void DoDisplayChanged()
{
if (DisplayChanged != null)
DisplayChanged.Invoke(this, EventArgs.Empty);
}
}
Your form will create the thread and add the relevant events, in this case DisplayChanged.
private void Main_Load(object sender, EventArgs e)
{
MyFunctionality func = new MyFunctionality();
func.DisplayChanged += new EventHandler(Func_DisplayChanged);
func.RunWorkerAsync();
}
Lastly, we just need to make a small change in Program.cs to either (A) instantiate our main form, which in turn launches the thread hosting our functionality, or (B) directly instantiate the thread without ever using our main form.
if (bDisplay)
Application.Run(new Main());
else
{
MyFunctionality func = new MyFunctionality ();
func.RunWorkerAsync();
Application.Run();
}
See, I told you you weren’t going to like it!

AfxGetMainWnd in C# June 27, 2007

Posted by codinglifestyle in C#, CodeProject, Winform.
Tags: , , , ,
add a comment

Back in the heady days of MFC men were men; toiling away in C++ and writing hundreds of lines to do things which are now ludicrously simple.  However, we had a trick up our sleeves which doesn’t exist in today’s plastic fantastic .NET:

AfxGetMainWnd() and AfxGetApp() were two fantastic macros we could exploit from nearly anywhere in our app to get a pointer to our main window or application, respectively.

Yesterday I was writing a quick client/server app.  The server was a WinForm tray application whose main form consists of a simple list to display status messages.  I wanted the thread listening for incoming traffic to update the status list.  I really missed AfxGetMainWnd() as I had a hard time finding a way to get a handle to my main window.  Because this was a tray app, which starts minimized, I was unable to use _FormMain.ActiveForm or Process.GetCurrentProcess().MainWindowHandle.  In fact, MainWindowHandle, was interesting as when I double-clicked the tray icon (opening the main window) the handle became valid and I could use it.  But the minute a minimized the window back to the tray the handle became null again.  So I was stuck with no reliable way of getting a handle to talk to my main window.

There may have been a better way, but this was a simple project and I wanted to move on quick.  I changed my main window to be a singleton, hiding the constructor and exposing an Instance variable which returned the one instance of the form.

 

To do this, lets look at the constructor:

 

private _FormMain()

{

InitializeComponent();

}

 

private static _FormMain m_Form = null;

public static _FormMain Instance

{

get

{

if (m_Form == null)

m_Form = new _FormMain();

return m_Form;

}

}

We then need to make a simple change in program.cs:

//Application.Run(new _FormMain());

Application.Run(_FormMain.Instance);

And lastly, to handle calls to from another thread:

// This delegate enables asynchronous calls from other threads

delegate void AddMessageCallback(string sMsg, Color c);

 

public void AddMessage(string sMsg, Color c)

{

if (_ListStatus.InvokeRequired)

{

AddMessageCallback amc = new AddMessageCallback(AddMessage);

this.Invoke(amc, new object[] { sMsg, c });

}

else

{

ListViewItem lvi = new ListViewItem(sMsg);

lvi.ForeColor = c;

_ListStatus.Items.Add(lvi);

_ListStatus.Refresh();

}

}

Note the delegate/invoke code used in AddMessage is straight from MSDN’s How to make thread-safe calls to Winform controls.

This way, I was able to simply call the following from anywhere in my code:

 


_FormMain.Instance.AddMessage(“Client has connected…”, Color.Green);

Capture All Keystrokes aka Accelerators aka Shortcuts June 14, 2007

Posted by codinglifestyle in C#, Winform.
Tags: , , , ,
add a comment

I have a custom ListView control which supports editing items. The WinForm application has lots of accelerators (shortcuts) such as DEL to delete an item as well as CTRL-C, CTRL-X, and CTRL-P for copy and paste operations. The problem I encountered was while editing the TextBox passed these keystrokes on to the application. This meant that copy and paste didn’t work within the TextBox because they were handled by the application. Worse, when using the delete key in the TextBox the user was prompted if they wanted to delete the item (again, an application shortcut).

 

The solution was simple, consume those keystrokes. Many examples test for WM_KEYDOWN and consume some messages and pass on others. I simply wanted all keystrokes to go to my TextBox while it was active.

 

    public class ListViewTextBox : TextBox

    {      

        //While editing, we want to capture all keystrokes

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)

        {

            return false;

        }

    }

 

CWaitCursor for WinForms September 1, 2006

Posted by codinglifestyle in C#, Winform.
Tags: , , ,
add a comment

There is no doubt, developing Windows applications in .NET is miles easier than in the bad old days of MFC.  However, there are times when MFC got it right and I found one of them this week.  MFC had a handy class called CWaitCursor.  You simply instantiated a local variable which would change the cursor to the wait icon.  When the local variable went out of scope, the cursor changed back.  I couldn’t find this functionality in .NET, but it was simple enough to duplicate:

    internal class CWaitCursor : IDisposable

    {

        private Form m_Form;

 

        public CWaitCursor(Form form)

        {

            m_Form = form;

            m_Form.Cursor = Cursors.WaitCursor;

        }

 

        public void Dispose()

        {

            m_Form.Cursor = Cursors.Default;

        }

    }

 

To use in your form create a local instance and pass the this pointer.  The using can be used to guarantee an immediate call to Dispose() like so:

    using (new CWaitCursor(this))

    {

        

    }

Exposing C# controls as Active-X and the effects on developing with the download cache January 5, 2006

Posted by codinglifestyle in C#, Javascript, Winform.
Tags: , , , ,
add a comment

A C# assembly can be easily exposed as an Active-X control using the object tag like so:

<OBJECT id=”Bob” classid=”url to assembly#full name of control></OBJECT>

So for example my assembly is located in a virtual directory on server VM2003S2:88 called MyObject.  MyObject has a namespace of Chris.ActiveX.Controls and the class is MyControl.  My object tag would be: http://vm2003s2:88/myobject.dll#chris.activex.controls.mycontrol

This complete I can now use javascript to talk to “Bob” via MyControl’s public methods.  This is all well and good however you may experience frustration in seeing your updates during development.

The assembly is automatically pulled down to the download cache which is located in c:\windows\assembly\downloads on Win2003.  Unfortunately, its not smart enough to read version strings and realize a newer copy is available on the server.  Therefore you must clear this cache using:

gacutil /cdl

But that’s not all!  A copy is also made in your temporary internet files.  You won’t be able to simply delete it using del from the post-build event as Window’s treats the directory as special.  So either write a small program utilizing the API to delete your assembly from the cache or use one of the miriad of privacy utilities to clean this for you.  I found CleanUp! which can be quietly executed from the command line and added to my post build event.  The result is the assembly is pulled every time (on the development machine) so you see your changes with each build.

Here is a good example explaining Embedded Windows User Controls into Internet Explorer:

http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=187