Winforms Asynchronously Pattern UI Update - Generalization Required

Customization: MDI main form with progress bar and label.

The code is in the main form.

public delegate void UpdateMainProgressDelegate(string message, bool isProgressBarStopped); private void UpdateMainProgress(string message, bool isProgressBarStopped) { // make sure we are running on the right thread to be // updating this form controls. if (InvokeRequired == false) { // we are running on the right thread. Have your way with me! bsStatusMessage.Caption = message + " [ " + System.DateTime.Now.ToShortTimeString() + " ]"; progressBarStatus.Stopped = isProgressBarStopped; } else { // we are running on the wrong thread. // Transfer control to the correct thread! Invoke(new ApplicationLevelValues.UpdateMainProgressDelegate(UpdateMainProgress), message, isProgressBarStopped); } } 

Children's uniform

 private readonly ApplicationLevelValues.UpdateMainProgressDelegate _UpdateMainForm; private void btnX_Click(object sender, EventArgs e) { _UpdateMainForm.BeginInvoke("StartA", false, null, null); try { if(UpdateOperationA()) { _UpdateMainForm.BeginInvoke("CompletedA", true, null, null); } else { _UpdateMainForm.BeginInvoke("CanceledA", true, null, null); } } catch (System.Exception ex) { _UpdateMainForm.BeginInvoke("ErrorA", true, null, null); throw ex; } } 

Its work is great, but for N or N buttons I have to write the same code over and over again. Is there a way that this can be generalized, or some other way to update the user interface.

+4
source share
3 answers

This is a really simple refactoring problem if all your operations can be represented as one type of delegate. eg:.

 private void RunOperationWithMainProgressFeedback( Func<bool> operation, string startMessage, string completionMessage, string cancellationMessage, string errorMessage) { this._UpdateMainForm.BeginInvoke(startMessage, false, null, null); try { if (operation.Invoke()) { this._UpdateMainForm.BeginInvoke(completionMessage, true, null, null); } else { this._UpdateMainForm.BeginInvoke(cancellationMessage, true, null, null); } } catch (Exception) { this._UpdateMainForm.BeginInvoke(errorMessage, true, null, null); throw; } } private void btnX_Click(object sender, EventArgs e) { this.RunOperationWithMainProgressFeedback( this.UpdateOperationA, "StartA", "CompletedA", "CanceledA", "ErrorA"); } 

Although you can use a dictionary to store argument values โ€‹โ€‹(as suggested in a previous VinayC answer), this is optional. Personally, I would avoid this for reasons of readability as well as performance, but ymmv ...

+4
source

You can create a dictionary / list containing the relevant data for each button. This data will include the name of the operation and the delegate to invoke the operation, etc. Now you have a common event handler for all buttons that will be searched in the dictionary to get the name and operation of the work that needs to be performed.

Another way would be to create your custom button inherited from the button, and the custom button can have these properties to store the data above. Choose as you wish.

+2
source

I'm tired of solving this same problem again and again, so I wrote a common set of classes to solve this problem. The basic idea is this:

  • Create an abstract Progress object with fields that interest you, such as statusMessage.

  • Create an abstract job object. Job as a stream, but in addition to Main and Start, it has the UpdateProgress (Progress p) method. UpdateProgress copies the current progress to p. This is usually the only place locks will be executed.

  • In your specific use, subclass Progress and add fields as needed. Then subclass Job to hold on to the stuff you want to do and implement UpdateProgress.

  • In the user interface, create a timer whose main call is made. Then UpdateProgress updates the ui.

Often timers are good enough for ui updates, and using the pull-and-syni-ui model is easier than using the push-changes model. Pull-and-sync-ui avoids a lot of cross-threading issues, and the user interface gets into weird states.

BONUS: make the Progress object an INotifyProperyChanged object and bind the data. (so you may not want to use an IDictionary).

+1
source

All Articles