how to communicate between several forms?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • john patohn
    New Member
    • Jan 2011
    • 11

    how to communicate between several forms?

    Hello,

    i'm trying to create an application which exists of 2 forms:
    a login form and a panel form.

    Now the login form speaks for itself, when i press login i send a request to my server and the server returns some things..

    I make use of a Form class which inits the form and a Client(Form login) class which do the networking.

    Now when i insert a wrong pasword for example, i want it that it sets a text on the login panel to invalid password.

    But the thing is that i get errors about cross threading when i set my text, EVEN if i use delegates to fix this.

    But the problem is that i acces the text via an instance of the form class: this.loginForm. text = "invalid pass";

    this code is in my Client class:
    Code:
            private LoginForm login = <retrieved from constructor param>
            delegate void updateLabelTextDelegate(string newText);
            private void updateLabelText(string newText)
            {
                if (this.login.label4.InvokeRequired)
                {
                    // this is worker thread
                    updateLabelTextDelegate del = new updateLabelTextDelegate(updateLabelText);
                    this.login.label4.Invoke(del, new object[] { newText });
                }
                else
                {
                    // this is UI thread
                    this.login.label4.Text = newText;
                }
            }
    so how do i need to fix this???

    Thanks in advance!
  • Samuel Jones
    New Member
    • Jan 2011
    • 48

    #2
    Ok, the code you've got has blown my mind... way too confusing!!!

    But there is a simple solution.

    I'm assuming that your 'panel' form is the main form and the 'login' form is just like a dialog that pops up?

    If so, follow these steps.
    login form is called frmLogin
    panel form is called frmPanel

    In the login form's code, declare this variable:
    Code:
    public Form main = new Form();
    On the panel form ABOVE frmLogin() method, declare this:
    Code:
    frmLogin login = new frmLogin();
    In your panel form's load event, put this:
    Code:
    login.main = this;
    login.ShowDialog();  //Can be just login.Show() as well but ShowDialog is better.
    Now wherever you want to communicate to 'panel' add this:
    Code:
    ((frmPanel)main).whateverPublicReference
    On the panel form use:
    Code:
    login.whateverPublicReference
    Hope this helps.

    Sam.

    Comment

    • john patohn
      New Member
      • Jan 2011
      • 11

      #3
      ok thanks for the fast reply!

      i'll try it!

      Comment

      • john patohn
        New Member
        • Jan 2011
        • 11

        #4
        Hmm it seems that this isn't what i want i think:

        i've 3 classes atm
        one login form class
        one panel form class
        one client class for networking(this is not a form!)

        so when the application starts up, i want it to show only my login form, i use the client class for networking, and when i have a wrong username/pass i want the text to be set at login class from the client class.. so i need an SAFE instance of the login class in my client class, but as stated above what i have now doesn't work.

        you are not using the client class in any step?

        thanks for the reply though :)

        Comment

        • Samuel Jones
          New Member
          • Jan 2011
          • 48

          #5
          I didn't notice you had a third class!! :D

          method still works though.

          Make the following modifications with my code.

          put 'public' into this line.

          Code:
          public frmLogin login = new frmLogin();
          On the panel form, add this code, similar to the other form.

          (btw the client is called netClient)

          Code:
          public netClient client = new netClient();
          on the client add the main reference.
          Code:
          public Form main = new Form();
          Modify the code as follows:
          Code:
          login.main = this;
          Hide();
          login.ShowDialog();
          Show();
          When you call the client, use this code:
          Code:
          client.main = this;
          client.run(); //Or similar
          Now to access the login form from the client use:
          Code:
          ((frmPanel)main).login.whateverPublicReference
          and to access the client from the login form use:
          Code:
          ((frmPanel)main).client.whateverPublicReference
          Try that one.

          Sam

          Comment

          • john patohn
            New Member
            • Jan 2011
            • 11

            #6
            thanks again, but i am confused now :(

            can u please say what i need to put in my login class to get a reference to client(this is not a form!), so i can call at my connect button in the login form:

            client.main = this;
            this.client.con nect();

            and what i need to put in my panel class to get a reference to my client ?
            and what is frmPanel ?
            and do i need todo Application.run (Panel()); ?

            so this is what i want:
            -user starts application,
            -login form pops up,
            -user fills in details and press connect button, that does client.connect( ) and the response is for example invalid username, then on the login form should appear login.label = "invalid username",
            -once he succesfully logged in, the login form dissappear and the panel form shows up,
            -the client is receiving packets still and now it needs to update something of the panel form panel.label = "hello";

            thanks!

            Comment

            • Samuel Jones
              New Member
              • Jan 2011
              • 48

              #7
              :D

              ok, apologies for the confusion.

              frmPanel is your panel form.
              frmLogin is your login form.
              netClient is your client CLASS.

              Add the code from my previous posts.

              Add the following code to frmPanel's load event.
              Code:
              {
                  this.Hide();  //Hides panel form
                  login.main = this;
                  client.main = this;
                  login.ShowDialog();  //Show login form
                  this.Show();
              }
              On login form's login button, put this code.
              Code:
              {
                  ((frmPanel)main).client.connect();
              }
              In the clients code at this when you want to send 'invalid username' back:
              Code:
              ((frmPanel)main).login.label.text = "Invalid Username";
              When the client wants to set the panel's label to say hello, use this code:
              Code:
              ((frmPanel)main).label.text = "Hello";

              The panels code should look like this.

              Code:
              namespace ...
              {
                 public class frmPanel : Form
                 {
              
                     //Copy from here
                     public netClient client = new netClient();
                     public frmLogin login = new frmLogin();
              
                     public frmPanel()
                     {
                         InitializeComponent();
                     }
              
                     private void frmPanel_Load(object sender, EventArgs e)
                      {
                          this.Hide();  //Hides panel form
                          login.main = this;
                          client.main = this;
                          login.ShowDialog();  //Show login form
                          this.Show();
                      }
              
              //...rest of program
              Try that.

              Sam

              Comment

              • john patohn
                New Member
                • Jan 2011
                • 11

                #8
                Ok that helped me alot, but now i see i got another problem.. :D

                i need to access the textbox from my login in my client class to get the name and pass.. how would i do that? :)

                thanks for the replies i really appreciate it!

                -----------------------------------------------------------------
                i fixed that but all this code did NOT help :(
                this is what i used: ((Form3)main).l ogin.label4.Tex t = "Invalid Username";
                it still says about cross threads invalid access on label
                ----------------------------------------------------------------

                Comment

                • Curtis Rutland
                  Recognized Expert Specialist
                  • Apr 2008
                  • 3264

                  #9
                  @Samuel, that's really not the best advice. What you're advising is tightly coupling multiple forms and classes. What if you have to change one? You have to start making changes all over the place.

                  A better pattern is to allow forms to raise events, and others to subscribe. To borrow an analogy, in a bingo parlor, the guy pulling the balls pulls one, reads it, and shouts it out, while all listeners check their boards to see if they have a match. He doesn't go to each of them and check for them, and they don't each have to go to him to check. It's broadcast, and each person responds in their own way, and neither cares what the other does.

                  To that point, I've recently written a tutorial to cover this exact point. Please have a read:



                  It shows the two best mechanisms for form interaction.

                  Comment

                  • john patohn
                    New Member
                    • Jan 2011
                    • 11

                    #10
                    thank you very much!
                    i will definately look at it and let you know if it helped!

                    Comment

                    • Samuel Jones
                      New Member
                      • Jan 2011
                      • 48

                      #11
                      @Curtis: I have happily been proven wrong. :D

                      This will help heaps in my current program and this user's program!

                      The one thing i can't see in your solution is how you would talk to the parent form? (in your example the Default Form and in John's problem the panel form)

                      I would think a conjunction of methods would work maybe?

                      @john: I'm stumped, i have no knowledge of such an error. maybe if you provide us with the full error script i could try to coax it out.

                      Try to solve your errors using Curtis's property method. ie.
                      Code:
                      // reference using login.error = "string" from panel form.
                      // reference using ((frmPanel)main).login.error = "string" from client.
                      
                      public string error
                      {
                         set
                         {
                             label4.Text = value;
                         }
                      }

                      Comment

                      • john patohn
                        New Member
                        • Jan 2011
                        • 11

                        #12
                        I've been a little busy, but im stuck again with the tutorial which was posted above...

                        the reason why i get corss thread exception: invalid access is because (i think atleast) i call the change from another thread, because i use asynchronous programming, and so i use a method OnReceive, which is triggered by another thread, so not the main thread where my form was created..

                        i tried adding the things with event system, but i still get the same exception, so events aren't thread safe neither.. which is normal

                        so any ideas how to solve this safely?


                        thanks in advance!

                        Comment

                        • Curtis Rutland
                          Recognized Expert Specialist
                          • Apr 2008
                          • 3264

                          #13
                          This should help:



                          Basically, you have to invoke the call instead of doing it directly.

                          Comment

                          • john patohn
                            New Member
                            • Jan 2011
                            • 11

                            #14
                            Thanks for the reply, but i tried that already (look first post..)

                            but the problem is that i access a label from another form.. so i need to safely access that class also.. atleast i think because it didn't work when i used delegates.

                            Im not sure about the design of multiple forms, stupid microsoft needs everything about forms handled on the main thread... which i have problems with..

                            if someone has any tutorial on how to make multiple forms and communicate between them..
                            THIS IS NOT FORM <-> FORM
                            more like
                            FORM <-event- CLIENT(NO FORM) -event-> OTHERFORM
                            the Client class handles the network therefor i don't have a form for that!

                            maybe my design is poor, but im still new at this.. please help me!

                            thanks!

                            Comment

                            • Curtis Rutland
                              Recognized Expert Specialist
                              • Apr 2008
                              • 3264

                              #15
                              Well, first of all, you absolutely don't need to directly access a label on a different form. That's poor design, and anyone who tells you to do that is wrong.

                              What you can do is expose a property or a method that will invoke itself if required. Here's a simplified example of what I think you're asking.

                              This is my "client" called AsyncClient. It exposes an event called "TaskComple te". When you call the BeginTask method, it starts a new thread, then sleeps that thread for 5 seconds, then triggers the event:

                              Code:
                               class AsyncClient
                               {
                                   public event EventHandler<EventArgs> TaskComplete;
                                   protected virtual void OnTaskComplete()
                                   {
                                       if (TaskComplete != null)
                                           TaskComplete(this, new EventArgs());
                                   }
                              
                                   public void BeginTask()
                                   {
                                       Task.Factory.StartNew(TaskMethod);
                                   }
                              
                                   private void TaskMethod()
                                   {
                                       Thread.Sleep(5000);
                                       OnTaskComplete();
                                   }
                               }
                              Form 1, when you click a button, launches Form2, updates it, and then starts a task. When the task is done, Form2 is updated again:

                              Code:
                              public partial class Form1 : Form
                              {
                                  private AsyncClient client;
                                  Form2 f2;
                                  public Form1()
                                  {
                                      InitializeComponent();
                                      f2 = new Form2();
                                      client = new AsyncClient();
                                      client.TaskComplete += new EventHandler<EventArgs>(client_TaskComplete);
                                  }
                              
                                  private void button1_Click(object sender, EventArgs e)
                                  {
                                      f2.Show();
                                      f2.TaskLabel = "Working";
                                      client.BeginTask();
                                  }
                              
                                  void client_TaskComplete(object sender, EventArgs e)
                                  {
                                      f2.TaskLabel = "Finished";
                                  }
                              
                                  
                              }
                              Now, Form2 is where the threading magic happens. Notice that instead of trying to directly update the label from other forms, I made a property called "TaskLabel" . And in the set accessor for this property, I check to see if an invoke is required. If so, I invoke it using an Action (which is a predefined delegate type that takes no parameters and returns no values). And if not, I set the label to whatever value provided:

                              Code:
                              public partial class Form2 : Form
                              {
                                  public string TaskLabel
                                  {
                                      get
                                      {
                                          return label1.Text;
                                      }
                                      set
                                      {
                                          if (InvokeRequired)
                                              Invoke(new Action(() => TaskLabel = value));
                                          else
                                              label1.Text = value;
                                      }
                                  }
                              
                                  public Form2()
                                  {
                                      InitializeComponent();
                                  }
                              }
                              So when TaskLabel is updated, if it is done so from another thread, it's invoked properly.

                              There you go. Communication from form, to client, to event, to another form, with a cross thread reference to boot.

                              Comment

                              Working...