2

Why does my first attempt to change a button's text in this code not work, while the third attempt does work ?

My user will have to wait a few seconds while the serial port connects. After that, I want to alert him that he has already connected (a second attempt can cause problems).

I wanted to let him know that things are okay, so he won't think "duh" and click twice.

Fail. The text change never appears.

Okay, why does the third change in button text work, but the first one does not ? I don't know if the second one works or not.

 /***********************************************************************
  * Button:  Connect Port                                               *
  ***********************************************************************/

 private void TheConnectPortButton_Click(object sender, EventArgs e) 
 {
     string OldText = TheConnectPortButton.Text;

     TheConnectPortButton.Text = "Busy, Please Wait";  /////// This never happens

     ButtonBoss.ButtonHandler_ConnectPort();

     TheConnectPortButton.Text = OldText;              /////// Not sure if this happens

     if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
     {
         TheConnectPortButton.Text = "Connected";      /////// This one does happen
     }

 }

the aUartSemaphoreThatTells.IfWeHaveConnectedToAPort is also used by the ButtonBoss routine to make sure he doesn't connect a second time, along with other button routines (e.g., make sure we are connected before we Tx/Rx or whatever).

I tried changing the code after the routine returns to look like this...

    if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
    {
        TheConnectPortButton.Text = "Connected";
    }
    else
    {
        TheConnectPortButton.Text = OldText;
    }

...and I still get the same result.

My guess (and that's all it is) is that threading is somehow involved in all this, and that the serial port routines trump the button text changing routines by some convolution that I don't follow properly at the moment.

Question: What do I need to do to get the text to change before the connection stuff hogs the system ?

(If that's what's happening)

Question 2: If I can't make this happen, I think I've read about "greying out" the buttons, or, I believe I saw somewhere that I can actually make a button go away right before the user's eyes so that he can't click it again. Links to example code would be welcome.

User.1
  • 2,562
  • 3
  • 33
  • 40
  • do you update your screen? Greying out button.enabled = false, hiding button.hide, look at msdn via google – dmaij Jan 10 '13 at 00:50
  • No, I don't update my screen. I'm off to find out what that means. Give me a link if you like, I'll follow it. – User.1 Jan 10 '13 at 00:54
  • Is this WinForms, WebForms, WPF? – John Saunders Jan 10 '13 at 00:55
  • start with http://msdn.microsoft.com/en-us/library/system.windows.forms.button(v=vs.100).aspx, go from there – dmaij Jan 10 '13 at 00:57
  • 1
    Very fundamental in UI code, covered well by any book about it. The quick fix, everybody prefers the quick fix over reading a book, is TheConnectPortButton.Update(). – Hans Passant Jan 10 '13 at 01:05
  • @HansPassant, Correct, this project was assigned to me, not by my choice, but by circumstances. The project was attempted three previous people who claimed and had credentials of training and experience in C#, over a period of 10 months, and now I'm doing the project. I claim a reasonable level of experience and ability in embedded systems and structured assembly language. I would love to help others who need that kind of help the way you and others here are helping me with this monster I'm currently facing. For the moment, I am learning C# from you guys and MSDN. – User.1 Jan 10 '13 at 16:21
  • @dmaij, yes, there it is on that page; the 311th (three hundred and eleventh) item on the page. Spot on ! – User.1 Jan 10 '13 at 16:36

1 Answers1

2

The problem is you're doing everything from one and the same event-handler consequently, so that the button has no time to get updated (redrawn). You could call Application.DoEvents(); method, but it's not a good idea at all, please, read Use of Application.DoEvents()

I think usually you're expected to push a time-consuming task into a separate thread, get progress report from it and update your GUI. There is a plenty of ways to create a "worker" thread and get some respond from it. For example, use a BackgroundWorker Class:

public partial class Form1 : Form
{
    public Form1() { InitializeComponent(); }

    private void button1_Click(object sender, EventArgs e)
    {
        BackgroundWorker w = new BackgroundWorker();
        w.WorkerReportsProgress = true;
        w.DoWork += new DoWorkEventHandler(w_DoWork);
        w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
        w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
        w.RunWorkerAsync();
        button1.Text = "Started";
    }

    //may influence GUI, as this event handler is run on the GUI thread
    void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        button1.Text = "Job is done";
    }
    //may influence GUI, as this event handler is run on the GUI thread
    void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        button1.Text = e.ProgressPercentage.ToString();
    }
    //runs in the worker thread...should do the actual job
    //may influence GUI through `ReportProgress`
    //or through `Invoke` method
    void w_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(500);
            worker.ReportProgress(10 * i);
        }
    }
}

Or you may use Task Class:

public partial class Form1 : Form
{
    public Form1() { InitializeComponent(); }

    private void button1_Click(object sender, EventArgs e)
    {
        new Task(DoTask).Start();
    }

    void DoTask()
    {
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(500);
            //here you need to update GUI through `Invoke` method
            //as the GUI may only be influenced from the the thread,
            //where it's created
            this.Invoke(new Action<int>((j) =>
                {
                    button1.Text = j.ToString();
                }), 10 * i);
        }
    }
}
Community
  • 1
  • 1
horgh
  • 17,918
  • 22
  • 68
  • 123
  • your suggestion on the BackGroundWorker thing looks like a good way to handle this. Fix my brain if I'm thinking wrong, but it looks like the only to parts of background worker that I would really want to use are the DoWork and the RunWorkerCompleted. Yes ? No ? The big time hog in all this is a `SerialPort.Open()` call, which is using USB drivers over which I have no knowledge or control at this moment. Whatever they're doing, they can take up to 30 seconds; generally 5 to 15 seconds. – User.1 Jan 10 '13 at 23:54
  • 1
    You only need to consider from where you're going to update your user interface (let it be button text, whatever)...If you want to do this right from the `DoWork` method, tou'll need to wrap all the GUI modification into `Invoke` method call...Another way is to call `ReportProgress` and pass there some predefined percentages, according to which you will set a proper button text in the `ProgressChanged` event handler. – horgh Jan 11 '13 at 00:03
  • @User.1 Yet another way may be to launch separate workers for different parts of the time-consuming task and update buttontext + launch the next part in the `RunWorkerCompleted` method. However I guess this is the least suitable approach for your task – horgh Jan 11 '13 at 00:04
  • For the moment at hand, this is just one task, something the user has to do before the rest of the app can do anything. We have to have the ports talking first before we can make the app do its designed task. Whatever, whatever, these look like good ideas. Thanks – User.1 Jan 11 '13 at 00:08