Modify Windows Form from another after Application::Run() C++

johnny_device
johnny_device used Ask the Experts™
on
This question relates to 'Microsoft Visual C++ Express'
Windows Forms application
-------------------------------------------------------------------------
I have a program with two Forms.  For Form1 to be able to modify Form2, I have instantiated an object of the Form2 Class within the Form1 Class, like this:
.........................................................................................
 public ref class Form1 : public System::Windows::Forms::Form
  {
    static Form2 ^ Form2Obj = gcnew Form2();
    public:      
      Form1(void)
      {
        InitializeComponent();
        .......... etc.
.........................................................................................
(thanks to RV for telling me about this, but I clearly still don't "get it")

I thought I'd be high and dry then, and be able to modify Form2 from Form1 like this:
Form2Obj->label1->Text = "TEST 1"; (for example).
However, this is only fine so long as I don't need to modify Form2 after it is run as an application in a separate thread, or after the dialogue is shown.  For example, if I do this:
Form2Obj->label1->Text = "TEST 1";
Form2Obj->ShowDialog();
Form2Obj->label1->Text = "TEST 2";
, then the Form2 window shows 'TEST 1' ( 'TEST 2' does not show up on the label Text).

How do I update the fields on Form2 dynamically, after Application::Run or ShowDialog?  Do I need to set up some event-driven dialogue between the two Forms?

What I want to achieve is to have Form1 take care of network connections; Form2 to run in a separate Thread and accept user input; functions in Form1 to be able to update the Form2 GUI dynamically as network events occur.


Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®

Commented:
When accessing a control's property (i.e. label1.Text), you need to do it from the Form's Thread.

You need to use the invoke method on a Control to change thread. (have a look here: http://msdn.microsoft.com/en-us/library/a1hetckb.aspx)

I'd recommend using the Invoke method on your Form2 instance.

Hopefully this will help you.

Author

Commented:
Thanks you for your prompt reply, disrupt (short and sweet).

The structure I have is already substantially the same as described at that URL, except for the fact that ShowDialog() is replaced with Show().  It is true that, in a simple scenario, invoking a modeless windows does allow Form1 to continue to modify Form2.

However, I am not sure how to apply this to the situation I'm asking about.  The link you've given doesn't go into how it works mutually, nor how to achieve this in separate Threads.

I can call Form2Obj->ShowDialog(); in a separate Thread function, but not Show(), which just hangs up.  I am also losing some graphic elements if I use Show() outside a separate Thread, rather than ShowDialog() - not sure why this is, yet.  Therefore, if I can modify a second Form started with ShowDialog(), or Application::Run(), so much the better - perhaps this is a contradiction in terms ... .

The other angle is how a button click on Form2 modifies Form1, as opposed to the other way round.  This click needs to call a public function in Form1, but I'm not sure how the linker picks this up; I've made a forward declaration at the top of Form2.h, but the linker does not make the connection, even in a simple setup without separate Threads.

Hi Neun123 - I've just read your post.  I'll need a bit of time to look into it ...thanks ...


jd


Author

Commented:
After a bit of experimentation, I have discovered that I can change Form2 if I place ShowDialog() in a new Thread, and then use the simple assignment  'Form2Obj->label1->Text = "TEST 1 "' after starting the Thread.  However, this is, presumably, not "safe" for the reasons Neun123 mentioned, so I have tried the following code to change it via an Invoke method:

delegate void SetTextDelegate(String^ text);
private: void SetText(String^ text) {
  if (Form2Obj->label1->InvokeRequired) {
    SetTextDelegate^ d = gcnew SetTextDelegate(this, &Form1::SetText);
    this->Invoke(d, gcnew array<Object^> { text });
  }
  else {
    Form2Obj->label1->Text = text;
  }

If I call SetText() straight after starting the ShowDialog() Thread, then the application hangs.  Any idea what's wrong?


}

Commented:
You should use

 
Form2Obj->label1->Invoke(d, gcnew array<Object^> { text });

Open in new window


to replace

this->Invoke(d, gcnew array<Object^> { text });

because "this" might not be on the right thread.

Also, you could add a try {} catch{} in SetText to make sure you don't have an exception popping out silently on a background thread (and see if you have one when the application hangs).

Author

Commented:
Much obliged, Neun123, thank you.

Could you help me out with the syntax for modifying Form1 from Form2?  As shown above, I have the Form2 Class encapsulated within the Form1 Class, and it looks like I can change Form2 from Form1; however, I'm a bit stuck with GUI elements (in Form2) invoking functions based in the Form1 Class.  I'm setting up the relevant '_Click' function in Form2, and then calling a Form1 function from within it; I've got a forward declaration for the Form1 function, but the linker does not recognise it.  I figured that if it was forward declared, and given that it's in the same namespace, it would be okay, but evidently not.

jd.

Commented:
A couple of ideas spring to mind:

1. Adding a reference to Form1 in Form2 (any reason Form2's variable is declared static?)
 --> That would require having a property in Form2 of type Form1, which you would set when you create your Form1 object, and then using that property to call Form1's functions.

2. Handling events from Form2 to Form1, which would allow for multiple consumers, but would require some work to create those events in Form2, register handlers in Form1 and handle the disposal of those handlers

3. Add a class in between Form1 and Form2, which references both objects, and exposes methods that both can call (each of those method would redirect the call to the correct FormX instance (1 or 2)) Both objects (Form1 and Form2) would need a reference (Property) that points to that in-between object.
Depending on your needs, you can add a level of abstraction (using Interfaces instead of classes in the in-between class). You could also handle multiple Form1 instances for a single Form2 instance (if required)

Option 3 seems the simplest to implement, but the choice is all yours (depending on your requirements)

Author

Commented:


Thank you again, Neun123.  I'll need a bit of time to mull over your answer ... .

Author

Commented:

Hello again, Neun123.

re. solution 1., Form2 is declared as static because otherwise it will not compile:
'only static data members can be initialized inside a ref class or value type'.
As you can see, an instance of Form2 is created when Form1 is initialized.  This was a step I did in trying to get the Forms to inter-communicate.  I'm not sure in practice how your first solution would work with the arrangement I've got at the moment.  When you say "add a reference to Form1 in Form2", are you talking about a slightly different scenario, where each Form is independent, rather than one encapsulated in another?  If not, I'm not sure how to avoid recursive header declaration problems.

Unfortunately, I'm not clear how to implement any of your solutions in practice.  The meat of the code is relatively easy to implement, and seems to work okay, but just this inter-communication between Forms is holding me up.  I can envisage the solutions you suggest, but can't get it to work syntactically.  Do you know of any online code examples of implementations in .NET managed C++ where this mutual Form interaction is enabled?    It would help a lot to see the actual code.
Commented:
Hello, Sorry for the late response. You were right, you need a level of abstraction (interfaces) to avoid the recursive header declaration problems.

I went ahead and built a little sample solution for you. Zipped it here:
http://www.sendspace.com/file/ipd915

Hopefully this is what you are looking for.

Here a some code snippets for future reference (if the link becomes dead), each snippet goes in a different file, the first 2 including the third:

 
public ref class Form1 : public System::Windows::Forms::Form, CPPForms::IForm1

//...

    public: virtual void UpdateLabel1(System::String^ text)
    {
        SetText(text);
    }
public:
		Form1(void)
		{
			InitializeComponent();
			Form2Obj = gcnew Form2((CPPForms::IForm1^)this);
			((CPPForms::Form2^)Form2Obj)->Show();
		}

private: CPPForms::IForm2^ Form2Obj;

Open in new window


 
public ref class Form2 : public System::Windows::Forms::Form, CPPForms::IForm2

//...

	public: virtual void UpdateLabel2(System::String^ text)
		{
			SetText(text);
		}
		
		Form2(CPPForms::IForm1^ form1)
		{
			InitializeComponent();
			Form1Obj = form1;
		}
private: CPPForms::IForm1^ Form1Obj;

Open in new window


 
namespace CPPForms
{
public interface class IForm1
	{
	public: void UpdateLabel1(System::String^ text);
	};
	
public interface class IForm2
	{
	public: void UpdateLabel2(System::String^ text);
	};

}

Open in new window

Author

Commented:
Neun123 -

You are a star!  I cannot really ask for more.  I will need a bit of time to get to grips with implementing this fully, but if any other new questions arise, I will post fresh threads (you've earnt your points!).

Best Regards,



jd

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial