0

I have a problem changing text from another class in another namespace. I have the first Form1 class :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{  
    public partial class Form1 : Form
    {
        static Form1 mainForm;

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool AllocConsole();

        public static String LinkToApi = "http://google.com/api/";
        public static Comunicator comunicator;
        public static int debug = 5;

        public Form1()
        {
            InitializeComponent();
            AllocConsole(); // allow console

            if(Form1.debug >= 3) Console.WriteLine("Application started");

            comunicator = new Comunicator();

            mainForm = this;
        }

        private void TestButton_Click(object sender, EventArgs e)
        {
            TestButton.Text = "Loading";
            comunicator.TestConnection();
        }
    }
}

and this Comunicator class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Collections.Specialized;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using System.IO;
using System.Threading;

namespace WindowsFormsApplication1
{
    public class Comunicator
    {
        private String action = "idle";

        public static Thread Start(Action action)
        {
            Thread thread = new Thread(() => { action(); });
            thread.Start();
            return thread;
        }

        public Comunicator()
        {
        }

        public void TestConnection()
        {
            if (Form1.debug >= 3) Console.WriteLine("Testing connection");

            // thread test
            Start(new Action(ApiTest));
        }

        public void ApiTest()
        {
            if (Form1.debug >= 3) Console.WriteLine("API test begin");

            // Create a request for the URL.        
            WebRequest request = WebRequest.Create("http://www.bogotobogo.com/index.php");

            // If required by the server, set the credentials.
            request.Credentials = CredentialCache.DefaultCredentials;

            // Get the response.
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            // Display the status.
            Console.WriteLine(response.StatusDescription);

            // Get the stream containing content returned by the server.
            Stream dataStream = response.GetResponseStream();

            // Open the stream using a StreamReader for easy access.
            StreamReader reader = new StreamReader(dataStream);

            // Read the content.
            string responseFromServer = reader.ReadToEnd();

            // Display the content.
            Console.WriteLine(responseFromServer);

            // Cleanup the streams and the response.
            reader.Close();
            dataStream.Close();
            response.Close();

            // Console.Read();

            if (Form1.debug >= 3) Console.WriteLine("API test end");
            // Form1.StaticTestButton.Text = "Loaded";   <---- CHANGE HERE
        }
    }
}

which is not even a form class (I want to keep everything nice and clean). I want to change the TestButton text into "LOADED" but i get an error when I try to do that as if Form1.TestButton does not exist in Comunicator class.

I have tried to instantiate the class, I made a couple of variables static ... nothing, still getting error.

What is the problem? How may I solve this?

The request must be asynchronous, that's why I am using threads.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
Damian
  • 761
  • 1
  • 9
  • 26
  • 1
    Did you look at this http://stackoverflow.com/questions/7959005/update-ui-from-background-thread? – Derek Van Cuyk Oct 14 '15 at 19:12
  • 2
    You have a major separation of concern problem. The only thing that should be responsible for changing the state of the UI is the UI. – Daniel Mann Oct 14 '15 at 19:12
  • Is there any exception? In which line? If not, what happens or what exactly does not happen? Until which point did you follow the program's execution (i.e. debug)? – Thomas Weller Oct 14 '15 at 19:16
  • 1
    "I want to keep everything nice and clean". And at the same time you want to set a form(!) button(!) text(!) that you neither own, nor as it turns out even have access? Just putting a method in a separate class does not make it cleaner. – Ivan Stoev Oct 14 '15 at 19:21
  • I would use a controller to keep track of your forms and communicators. Then add some events for when "things" happen. For example, have the communicator raise an event when a connection/whatever you need happens, and have your form(s) subscribe to that event so they can then update their own buttons/state/etc. – JamesL Oct 14 '15 at 19:30
  • @JamesL -> how may i do that? – Damian Oct 14 '15 at 20:49

2 Answers2

1

You should separate concerns, and you shouldn't communicate with UI in class which is not related to UI. You should rewrite your code. But as quick fix you should do the following.

In class Comunicator, you can do such field.

private readonly Action<string> _notifySimpleMessageAction;

Then add to Communicator constructor parameter notifyFunction. Code in constructor:

_notifySimpleMessageAction = notifyFunction

After that you should create Communicator in following manner:

communicator = new Communicator((notification)=>
    {
                StaticTestButton.BeginInvoke((MethodInvoker)(() => StaticTestButton.AppendText(notification)));
            });

Then at the end of your method you should do

_notifySimpleMessageAction("Loaded")    
0

Controller class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ControllerDemonstrator
{
    public class Controller
    {
        public event EventHandler CommunicatorDataLoaded;
        public event EventHandler FormTestConnection;

        private Form1 _form;
        private Communicator _communicator;

        public Form1 MainForm
        {
            get { return _form; }
        }

        public Controller()
        {
            _form = new Form1(this);
            _form.TestConnection += _form_TestConnection;
            _form.FormClosed += _form_FormClosed;
            _communicator = new Communicator(this);
            _communicator.DataLoaded += _communicator_DataLoaded;
        }

        public void Start()
        {
            _form.Show();
        }

        void _form_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)
        {
            //  put any code to clean up the communicator resources (if needed) here
            //  --------------------------------------------------------------------
            _communicator = null;
            //  Then exit
            //  ---------
            Application.Exit();
        }

        private void _communicator_DataLoaded(object sender, EventArgs e)
        {
            if (null != CommunicatorDataLoaded)
            {
                CommunicatorDataLoaded(sender, e);
            }
        }

        private void _form_TestConnection(object sender, EventArgs e)
        {
            if (null != FormTestConnection)
            {
                FormTestConnection(sender, e);
            }
        }
    }
}

Basic form with one button (_testButton):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ControllerDemonstrator
{
    public partial class Form1 : Form
    {
        public event EventHandler TestConnection;

        public Form1(Controller controller)
        {
            InitializeComponent();
            controller.CommunicatorDataLoaded += controller_CommunicatorDataLoaded;
        }

        void controller_CommunicatorDataLoaded(object sender, EventArgs e)
        {
            _testButton.Text = "Loaded";
        }

        private void _testButton_Click(object sender, EventArgs e)
        {
            if (null != TestConnection)
            {
                TestConnection(this, new EventArgs());
            }
        }
    }
}

Communicator class (everything has been stripped out, you will need to add in your logic):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ControllerDemonstrator
{
    public class Communicator
    {
        public event EventHandler DataLoaded;

        public Communicator(Controller controller)
        {
            controller.FormTestConnection += controller_FormTestConnection;
        }

        private void controller_FormTestConnection(object sender, EventArgs e)
        {
            //  put your code that does the connection here
            //  -------------------------------------------
            if (null != DataLoaded)
            {
                DataLoaded(this, new EventArgs());
            }
        }
    }
}

And in your Program.cs (assuming that is how you are starting your application):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ControllerDemonstrator
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Controller c = new Controller();
            Application.Run(c.MainForm);
        }
    }
}

With this kind of design, the communicator doesn't know about the form and vice verse. You can expand it out to have different kind's of communicators/forms/etc and have the controller keep track of everything. It is also much easier to test code like this as you can test each separate piece on it's own since they don't depend on each other. This is a quick and dirty implementation. Do some research on the Model View Controller design pattern (not Microsoft MVC for asp.Net, but the actual design pattern). It is more code up-front to code an application with the MVC design pattern but it makes it easier to test and more maintainable.

JamesL
  • 319
  • 2
  • 4
  • 11
  • i know `MVC` i used `CodeIgniter` a lot, but this is `C#`, another story ... i see here that there are 2 concept i am not fully familiarized with, first i need to learn about `events` and second just found out from you that i might fave to change one line in `Program.cs` (`Controller c = new Controller();`) ... i thought that `Program.cs` is there to never be touch ... but i ask you, wouldn't be more easy as i made it now, i have a `public` variable in `Form1`, i change it from `Comunicator` and i have a `Timer1_tick` that checks for any change, what do you think about this? – Damian Oct 31 '15 at 12:52