I've tried using a few methods but can't seem to pass along the label to the new thread or have the secondary thread be able to access the label and refresh the form on the form thread. I'm trying to have it multi-threaded so the interface doesn't lock up when running commands to remote devices. Any help in having a way to update the label and refresh the form from a secondary thread would be appreciated! Here is my MyForm.h file and MyForm.cpp file:
#include <iostream>
#include <conio.h>
#include <windows.h>
namespace StudioControl2 {
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Threading;
using namespace System::Threading::Tasks;
using namespace std;
bool GUIEvent = false;
/// <summary>
/// Summary for MyForm
/// </summary>
public ref class MyForm : public System::Windows::Forms::Form
{
public:
MyForm (void)
{
InitializeComponent();
//
//TODO: Add the constructor code here
//
}
protected:
/// <summary>
/// Clean up any resources being used.
/// </summary>
~MyForm()
{
if (components)
{
delete components;
}
}
private: System::Windows::Forms::Button^ WindowTVVmixSelect;
private: System::Windows::Forms::Label^ StatusOutput;
private: System::ComponentModel::IContainer^ components;
protected:
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)
{
System::ComponentModel::ComponentResourceManager^ resources = (gcnew System::ComponentModel::ComponentResourceManager(MyForm::typeid));
this->WindowTVVmixSelect = (gcnew System::Windows::Forms::Button());
this->StatusOutput = (gcnew System::Windows::Forms::Label());
(cli::safe_cast<System::ComponentModel::ISupportInitialize^>(this->pictureBox1))->BeginInit();
this->SuspendLayout();
//
// WindowTVVmixSelect
//
this->WindowTVVmixSelect->Anchor = System::Windows::Forms::AnchorStyles::None;
this->WindowTVVmixSelect->Location = System::Drawing::Point(31, 400);
this->WindowTVVmixSelect->Margin = System::Windows::Forms::Padding(5);
this->WindowTVVmixSelect->Name = L"WindowTVVmixSelect";
this->WindowTVVmixSelect->Size = System::Drawing::Size(120, 30);
this->WindowTVVmixSelect->TabIndex = 10;
this->WindowTVVmixSelect->Text = L"Vmix";
this->WindowTVVmixSelect->UseVisualStyleBackColor = true;
this->WindowTVVmixSelect->Click += gcnew System::EventHandler(this, &MyForm::WindowTVVmixSelect_Click);
//
// StatusOutput
//
this->StatusOutput->Anchor = System::Windows::Forms::AnchorStyles::None;
this->StatusOutput->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D;
this->StatusOutput->ForeColor = System::Drawing::Color::Red;
this->StatusOutput->Location = System::Drawing::Point(232, 612);
this->StatusOutput->Margin = System::Windows::Forms::Padding(5);
this->StatusOutput->Name = L"StatusOutput";
this->StatusOutput->Size = System::Drawing::Size(120, 30);
this->StatusOutput->TabIndex = 20;
this->StatusOutput->TextAlign = System::Drawing::ContentAlignment::MiddleCenter;
//
// MyForm
//
this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
this->ClientSize = System::Drawing::Size(584, 661);
this->Controls->Add(this->WindowTVVmixSelect);
this->MinimumSize = System::Drawing::Size(600, 700);
this->Name = L"MyForm";
this->Text = L"Studio Control";
(cli::safe_cast<System::ComponentModel::ISupportInitialize^>(this->pictureBox1))->EndInit();
this->ResumeLayout(false);
this->PerformLayout();
}
#pragma endregion
private: System::Void WindowTVVmixSelect_Click(System::Object^ sender, System::EventArgs^ e) {
if (GUIEvent == false) {
GUIEvent = true;
Console::WriteLine("Window TV Vmix Select");
WindowHDMIStatus -> Text = "Vmix";
StatusOutput->Text = "Please Wait";
MyForm::Refresh();
//Creation of new thread.
ThreadWindowTVVmixSelect^ o1 = gcnew ThreadWindowTVVmixSelect();
Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadWindowTVVmixSelect::ThreadEntryPoint));
t1->Name = "t1";
t1->Start();
}
}
ref class ThreadWindowTVVmixSelect
{
public:
void ThreadEntryPoint()
{
Sleep(2000);
Console::WriteLine("Terminating Thread Window TV Vmix Select");
//Trying to update and refresh label in MyForm.
GUIEvent = false;
}
};
};
}
#include "MyForm.h"
using namespace System;
using namespace System::Windows::Forms;
[STAThreadAttribute]
int main(array<String^>^ args) {
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
StudioControl2::MyForm form;
Application::Run(%form);
return 0;
}
I figured out how to make a Thread-Safe call from the same site I mentioned (https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls). There's a small symbol at the top right of the page (</>
) that changes the code examples to one of three options, C#, VB, or C++.
Here's the code I used for setting up a new thread, and running a thread safe invoke on the form label on the UI thread from the new thread:
private:
Thread^ Thread1;
private: System::Void WindowTVVmixSelect_Click(System::Object^ sender, System::EventArgs^ e) {
if (GUIEvent == false) {
GUIEvent = true;
Console::WriteLine("Window TV Vmix Select");
StatusOutput->Text = "Please Wait";
MyForm::Refresh();
//Creation of new thread.
this->Thread1 =
gcnew Thread(gcnew ThreadStart(this, &MyForm::ThreadWindowTVVmixSelect));
this->Thread1->Start();
}
}
private:
void ThreadWindowTVVmixSelect()
{
Sleep(3000);
//SetTextWindowHDMIStatus("Vmix");
SetTextStatusOutput("");
GUIEvent = false;
}
delegate void StringArgReturningVoidDelegate(String^ text);
//Sets the status label in the form
private:
void SetTextStatusOutput(String^ text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this->StatusOutput->InvokeRequired)
{
StringArgReturningVoidDelegate^ d =
gcnew StringArgReturningVoidDelegate(this, &MyForm::SetTextStatusOutput);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->StatusOutput->Text = text;
}
}
Hopefully this will help anyone else looking to update a C++ CLR form from another thread.