Search code examples
.netwinformsvisual-studio-2010visual-c++c++-cli

How to run a GUI form in another thread?


Please have a look at the following code

#pragma once


    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Threading;

    /// <summary>
    /// Summary for NotifyAlarm
    /// </summary>
    public ref class NotifyAlarm : public System::Windows::Forms::Form
    {
        int count;


    public:
        NotifyAlarm(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
            count = 10;
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~NotifyAlarm()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Label^  label1;
    protected: 
    private: System::Windows::Forms::Label^  secondsLabel;
    private: System::Windows::Forms::Label^  label2;
    private: System::Windows::Forms::Button^  sendNowBtn;
    private: System::Windows::Forms::Button^  cancelBtn;
    private: System::Windows::Forms::Timer^  timer1;
    private: System::ComponentModel::IContainer^  components;


    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>


#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->components = (gcnew System::ComponentModel::Container());
            this->label1 = (gcnew System::Windows::Forms::Label());
            this->secondsLabel = (gcnew System::Windows::Forms::Label());
            this->label2 = (gcnew System::Windows::Forms::Label());
            this->sendNowBtn = (gcnew System::Windows::Forms::Button());
            this->cancelBtn = (gcnew System::Windows::Forms::Button());
            this->timer1 = (gcnew System::Windows::Forms::Timer(this->components));
            this->SuspendLayout();
            // 
            // label1
            // 
            this->label1->AutoSize = true;
            this->label1->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->label1->Location = System::Drawing::Point(13, 27);
            this->label1->Name = L"label1";
            this->label1->Size = System::Drawing::Size(405, 20);
            this->label1->TabIndex = 0;
            this->label1->Text = L"Intruder Detected. An Email and SMS will be sent within ";
            // 
            // secondsLabel
            // 
            this->secondsLabel->AutoSize = true;
            this->secondsLabel->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->secondsLabel->ForeColor = System::Drawing::Color::Red;
            this->secondsLabel->Location = System::Drawing::Point(408, 27);
            this->secondsLabel->Name = L"secondsLabel";
            this->secondsLabel->Size = System::Drawing::Size(51, 20);
            this->secondsLabel->TabIndex = 1;
            this->secondsLabel->Text = L"label2";
            // 
            // label2
            // 
            this->label2->AutoSize = true;
            this->label2->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 12, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, 
                static_cast<System::Byte>(0)));
            this->label2->Location = System::Drawing::Point(465, 27);
            this->label2->Name = L"label2";
            this->label2->Size = System::Drawing::Size(69, 20);
            this->label2->TabIndex = 2;
            this->label2->Text = L"seconds";
            // 
            // sendNowBtn
            // 
            this->sendNowBtn->Location = System::Drawing::Point(370, 70);
            this->sendNowBtn->Name = L"sendNowBtn";
            this->sendNowBtn->Size = System::Drawing::Size(75, 23);
            this->sendNowBtn->TabIndex = 3;
            this->sendNowBtn->Text = L"Send Now";
            this->sendNowBtn->UseVisualStyleBackColor = true;
            this->sendNowBtn->Click += gcnew System::EventHandler(this, &NotifyAlarm::sendNowBtn_Click);
            // 
            // cancelBtn
            // 
            this->cancelBtn->Location = System::Drawing::Point(469, 70);
            this->cancelBtn->Name = L"cancelBtn";
            this->cancelBtn->Size = System::Drawing::Size(75, 23);
            this->cancelBtn->TabIndex = 4;
            this->cancelBtn->Text = L"Cancel";
            this->cancelBtn->UseVisualStyleBackColor = true;
            this->cancelBtn->Click += gcnew System::EventHandler(this, &NotifyAlarm::cancelBtn_Click);
            // 
            // timer1
            // 
            this->timer1->Enabled = true;
            this->timer1->Interval = 1000;
            this->timer1->Tick += gcnew System::EventHandler(this, &NotifyAlarm::timer1_Tick);
            // 
            // NotifyAlarm
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(566, 105);
            this->Controls->Add(this->cancelBtn);
            this->Controls->Add(this->sendNowBtn);
            this->Controls->Add(this->label2);
            this->Controls->Add(this->secondsLabel);
            this->Controls->Add(this->label1);
            this->Name = L"NotifyAlarm";
            this->Text = L"NotifyAlarm";
            this->ResumeLayout(false);
            this->PerformLayout();

        }
#pragma endregion
    private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) 
             {
                 count--;

                 if(count>0 || count==0)
                 {
                     secondsLabel->Text = ""+count;
                 } 
                 else
                 {
                     //code removed
                 }
             }
private: System::Void sendNowBtn_Click(System::Object^  sender, System::EventArgs^  e) 
         {
             timer1->Stop();

            //code removed
         }

public: System::Void showGUI() 
                  {
                      this->Show();
                  }



};

This is a kind of a Notification box which I am trying to open in a new thared. The reason is, the default thread is already overloaded. This is how I call this from another thread

na = gcnew NotifyAlarm();
        Thread ^alertThread = gcnew Thread(gcnew System::Threading::ThreadStart(na,&NotifyAlarm::showGUI));
        alertThread->Start();

Unfortunately when I am running this thread, I get the following error

enter image description here

This occurred when the code reaches here

 if(count>0 || count==0)
  {
        secondsLabel->Text = ""+count;
  } 

As you can see I am trying to update the Label in there.

So, how can I make this GUI form run in another thread without having these errors?

PS: I am not coming from .NET culture, instead I am coming from Java and C++.


Solution

  • You should use BeginInvoke to synchonize (dispatch) the call to the UI-thread:

    delegate void UpdateTextDelegate(int count);
    
    private: void DoUpdateText(int count)
    {
        ISynchronizeInvoke^ i = this;
    
        if (i->InvokeRequired)
        {
          UpdateTextDelegate^ tempDelegate =
            gcnew UpdateTextDelegate(this, &Form1::DoUpdateText);
          cli::array<System::Object^>^ args = gcnew cli::array<System::Object^>(1);
          args[0] = count;
          i->BeginInvoke(tempDelegate, args);
          return;
        }
    
        secondsLabel->Text = count.ToString();
    }
    

    Then you can call the DoUpdateText method from within an other thread.