Dailycode.info

Short solution for short problems

Track changes without binding controls

When you are working with windows forms .net without binding your controls to a data object, you still need to check if a control is changed or not. For example if a user closes a form without saving it, you might want to tell the user this and ask him to save or not.

This post will show you a very simple implementation that will easy the development effort. To use this, you will need to create control wrappers for every control that you want to auto implement the track changes functionality.

Let’s begin with creating or modifying an interface that is implemented by all controls that need change tracking. I will add the property IsDirty to the interface. The interface is called: IControlBase

 

#region Properties

 

bool IsDirty { get; set; }

 

#endregion

 

Next we implement the interface in our controls and set the IsDirty flag whenever a value is changed. I will give 2 examples:

Textbox:

#region Implementation of IControlBase

 

public bool IsDirty

{

    get;

    set;

}

#endregion

protected override void OnKeyPress(KeyPressEventArgs e)

{

    IsDirty = true;

    base.OnKeyPress(e);

}

 

Combobox:

#region Implementation of IControlBase

 

public bool IsDirty

{

    get;

    set;

}

 

 

protected override void OnTextChanged(EventArgs e)

{

    IsDirty = true;

    base.OnTextChanged(e);

}

 

So this will make sure that the events we want will set the IsDirty flag. For the combobox I had the problem that the event was raised when I filled it, so I created a function that will clear all IsDirty flags after loading the form:

 

public static void ClearIsDirty(Control currentControl)

{

 

    foreach (Control isDirtyctrl in currentControl.Controls)

    {

        if (isDirtyctrl.Controls.Count > 0)

        {

            ClearIsDirty(isDirtyctrl);

        }

        if (isDirtyctrl.Parent as IControlBase != null)

        {

            ((IControlBase)isDirtyctrl.Parent).IsDirty = false;

        }

    }

}

 

After the page is loaded and all controls are filled, I simply run this function and pass the parent control to it. So it will clear itself and all child controls. The control could be a panel or the form itself:

GlobalFunctions.ClearIsDirty(tpGeneral);

 

Then the last piece of code needs to be written when the user leaves the form without saving. So the onleave of your control is captured and then we call a second function that will check all controls of the form on their IsDirty flag. Here is the function that does the trick, put this function somewhere in your general functions namespace.

private static bool _isDirty=false;

 

public static bool CheckIsDirty(Control currentControl)

{

 

    if ((_isDirty == true))

    {

        return _isDirty;

    }

 

    foreach (Control isDirtyctrl in currentControl.Controls)

    {

        if (isDirtyctrl.Controls.Count > 0)

        {

            CheckIsDirty(isDirtyctrl);

        }

        else

        {

            if (isDirtyctrl.Parent as IControlBase != null)

            {

                if ((((IControlBase)isDirtyctrl.Parent).IsDirty == true))

                {

                    _isDirty = true;

                    return _isDirty;

                }

            }

        }

    }

    return _isDirty;

}

 

Here is the implementation of this function (I check 2 tabpages on the IsDirtyFlag, if the user wants to save I save the project. At the end I clear the IsDirty flags):

private void tbcontProject_Leave(object sender, EventArgs e)

{

    if (GlobalFunctions.CheckIsDirty(tpGeneral) || GlobalFunctions.CheckIsDirty(tpInfo))

    {

        DialogResult res = MessageBox.Show("Information has been changed, do you want to save?", "Information changed", MessageBoxButtons.YesNo);

        if (res == DialogResult.Yes)

        {

            SaveProject();

        }

        GlobalFunctions.ClearIsDirty(tpGeneral);

        GlobalFunctions.ClearIsDirty(tpInfo);

    }

 


Setting the property of a control from a seperate thread using an extension method.

In this post you can find a solution that implements an extension method to change the properties of a Windows Forms Control. It’s a large improvement for my previous post: http://www.dailycode.info/Blog/post/2011/04/18/Multi-Threading-and-accessing-controls-from-seperate-threads-in-WPF.aspx

Now this extension will provide the functionality on any control of your form. This is the function that does the magic:

/// <summary>  

/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.  

/// </summary>  

/// <param name="control"></param>  

/// <param name="code"></param>  

public static void UIThread(this Control @this, Action code)

{

    if (@this.InvokeRequired)

    {

        @this.BeginInvoke(code);

    }

    else

    {

        code.Invoke();

    }

}

 

Can it be more simpler? This can save a lot of developer time and is one of my latest high impact findings thanks to: http://stackoverflow.com/questions/661561/how-to-update-gui-from-another-thread-in-c

 

Here are some examples on  how to implement this:

Setting some text in a textbox from a separate thread:

this.UIThread(() => lblSubAction.Text = @"Reading Records...");

 

Changing the value of a progressbar:

 

this.UIThread(() => pgbSub.Value = pgbSub.Value + pgbSub.Step);

 

Setting a property of a third party control:

this.UIThread(() => numRecordsFound.EditValue = countForLambda);

 


Clear the controls from a .Net panel of container.

If you are working a lot with usercontrols and dynamlically adding them to the GUI, you might end up with the error saying that there are no more window handlers.

One of the reasons could be that you clear controls from your screen but they do not get discposed. Most of the time its just to many usercontrols, so add some paging or something.

But always try the following solution first. In stead of using the clear call to the panel, dispose each object in the panel. You can use this code:

    Private Shared Sub DisposeObjectsInPanel(ByVal pnl As Panel)

        For iCounter As Integer = 1 To pnl.Controls.Count Step 1

            If Not pnl.Controls(0) Is Nothing Then

                pnl.Controls(0).Dispose()

            End If

        Next

    End Sub


Multi-Threading or Cross threading and accessing controls from seperate threads in WPF

I started to build my first real WPF application. It’s a former windows application that I’m rebuilding and improving using WPF and WCF. Former it was Windows forms and Web services.

One of the first problems I encountered was when I was experimenting with multithreading. I start my main thread and from there I will start a background thread. This background thread will start sub thread of its own. Still not a big issue, until I wanted to report the status from my background thread to my main thread. At this point it’s slightly different in WPF then it was in windows forms. And for some reason Microsoft decided to hide some methods in Visual Studio, so this makes it even harder.

The base article from which I constructed my application can be found is Avalon Threading by Tyler Barton and Nick Kramer.

So it all starts when you start a process in a separate thread, I decided to start a background worker thread process like this:

 

/// <summary>

/// Loads the devices and starts Polling

/// </summary>

public void LoadDevices(List<ThinClient> thinClients)

{

    BackgroundWorker bw = new BackgroundWorker();         

    // this allows our worker to report progress during work        

    bw.WorkerReportsProgress = true;         

    // what to do in the background thread        

    bw.DoWork += new DoWorkEventHandler(delegate(object o, DoWorkEventArgs args)

        {

            LoadDevicesInThread(thinClients);       

        });         

   

    bw.RunWorkerAsync();

           

}

 

The actual work is done in the LoadDevicesInThread function. This function will loop over a list of TCP devices and will try to poll them, using a poll time out of 2 seconds. We do not want this to block the main thread, so that’s why it’s handled in the background. Still after each device I want the background thread to report the status to my GUI in the main thread.

That’s why I created these 3 methods. The background worker process is started in a sub class, where I created an event that will be raised whenever I want to log something back to the GUI.

 

public delegate void LogHandler(string type, string message);

 

public event LogHandler Log;

 

protected void OnLog(string type, string message)

{

    if (Log != null)

    {

        Log(type, message);

    }

}

 

So every time I call the OnLog method, the Log event  will be raised and captured by the GUI. Here were I do the binding to the event in the main window:

 

_ourDevices.Log += new Devices.LogHandler(_ourDevices_Log);

 

So the LogHandler will handle the loggings coming from the background thread and pass them to the GUI. This is where the tricky bit comes in. In Windows forms we had to call the InvokeRequired method, but when you look for this or something like this, you will find nothing in intellisense. Why you may ask. Because for WPF the InvokeRe quired doesn’t exists but has a “replacement” called CheckAccess. They decided to hide this method from the Visual Studio intellisense.  So when you want to invoke a control from a separate thread, this is the way it’s done:

 

public delegate void LogMessageDelegate(string type, string message);

 

void _ourDevices_Log(string type, string message)

{

    if (lstLogItems.CheckAccess())

    {

        lstLogItems.Items.Add(new InfoMessage(type, message));

    }

    else

    {

        lstLogItems.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new LogMessageDelegate(this._ourDevices_Log),type, message);

    }

}

 

So here you can see that when the ListView called lstLogItems is accessed from a separate thread, he will end up in the else and do a begininvoke. He will use the delegate function to accomplish this and pass the parameters to it.

So I hope it become a little clearer this way. I posted an extension method on my blog for windows forms cross threading: http://www.dailycode.info/Blog/post/2011/10/12/Setting-the-property-of-a-control-from-a-seperate-thread-using-an-extension-method.aspx. You can try to rewrite this for WPF, if the need comes I will do it and post this on the Blog.