如何在另一个线程中运行GUI表单?

时间:2013-08-19 09:07:41

标签: .net winforms visual-studio-2010 visual-c++ c++-cli

请查看以下代码

#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();
                  }



};

这是一种通知框,我试图在新版本中打开。原因是,默认线程已经过载。这就是我从另一个线程中调用它的方法

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

不幸的是,当我运行此线程时,我收到以下错误

enter image description here

当代码到达

时发生这种情况
 if(count>0 || count==0)
  {
        secondsLabel->Text = ""+count;
  } 

正如您所见,我正在尝试更新那里的标签。

那么,如何让这个GUI表单在另一个线程中运行而不会出现这些错误?

PS:我不是来自.NET文化,而是来自Java和C ++。

2 个答案:

答案 0 :(得分:4)

您应该使用BeginInvoke将调用同步(调度)到UI线程:

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();
}

然后你可以在另一个线程中调用DoUpdateText方法。

答案 1 :(得分:3)

不清楚Text属性赋值发生在哪个线程上。但显然它是在错误的线程上,你需要使用表单或标签的BeginInvoke()方法来编组调用。

请注意,在启动线程之前创建表单对象实例存在风险。规则是,实际创建窗口句柄(CreateHandle()调用)的任何线程都是窗口的所有者。如果表单的构造函数意外地创建了句柄,那么可以是错误的线程。仅通过在线程方法上创建表单对象来避免麻烦。

值得注意的是这有多危险。一个重要的麻烦制造者是SystemEvents类,许多控件订阅UserPreferenceChanged事件。他们这样做是为了在用户更改主题设置时重新绘制自己。此事件在其他情况下也会触发,锁定工作站是一个臭名昭着的麻烦来源。桌面开关可以触发事件。

SystemEvents有一个不值得羡慕的任务,就是在正确的线程上触发此事件。它不能,你给它两个线程可供选择。你的主UI线程和这个新的“gui线程”。其中一个将在错误的线程上触发事件。结果是死锁。在表单关闭且线程不再存在之后很久就会发生这种情况。

这很难处理。还有更多问题,窗口具有自己的生命,并且与应用程序中的其余窗口没有Z顺序关系。一个重要的问题是它习惯在主线程拥有的窗口下面显示 。用户无法看到它。这种问题需要通过使其成为一个拥有的窗口来解决,因此它保证是最高的。这也不起作用,当你调用Show(所有者)重载时,你会再次得到InvalidOperationException。

非常令人讨厌的问题,信息应该是清楚的:不要这样做。从来没有需要,程序的主线程可以处理任意数量的窗口。典型的错误是使用这样的通知窗口来隐藏在主线程上运行的代码中的缺陷并且花费太多时间,使GUI无响应。真正的解决方法是在工作线程上运行 代码。