Reputation: 23025
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
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++.
Upvotes: 0
Views: 1392
Reputation: 941545
It isn't clear on what thread the Text property assignment occurs. But clearly it is on the wrong thread, you need to use the form's or label's BeginInvoke() method to marshal the call.
Do note that it is risky to create the form object instance before starting the thread. The rule is that whatever thread actually created the window handle (CreateHandle() call) is the owner of the window. That can be the wrong thread if the form's constructor accidentally creates the handle. Stay out of trouble by only creating the form object on the thread method.
And it is worth noting how dangerous this is. A significant troublemaker is the SystemEvents class, lots of controls subscribe the UserPreferenceChanged event. They do so to repaint themselves when the user changes the theme settings. This event also fires in other cases, locking the workstation is a notorious source of trouble. The desktop switch can get the event fired.
SystemEvents has the unenviable task of firing this event on the correct thread. It can't, you give it two threads to choose from. Your main UI thread and this new "gui thread". One of them is going to get the event fired on the wrong thread. The result is deadlock. It can happen long after your form is closed and the thread no longer exists.
That's very hard to deal with. There are more problems, the window has a life of its own and has no Z-order relationship with the rest of the windows in your app. A significant problem with that is that it has a habit of displaying itself underneath a window owned by the main thread. The user can't see it. This kind of problem needs to be solved by making it an owned window so it is guaranteed to be top. That doesn't work either, you'll get the InvalidOperationException again when you call the Show(owner) overload.
Very yucky problems, the message ought to be clear: don't do this. There never is a need, the main thread of your program can handle any number of windows. The typical mistake is using such a notification window to hide a flaw in code that runs on the main thread and it taking too much time, making the GUI unresponsive. The real fix is to run that code on a worker thread.
Upvotes: 3
Reputation: 3684
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.
Upvotes: 4