Home > OS >  C# Calling WinForm Class from a different class to update text displayed on WinForm element
C# Calling WinForm Class from a different class to update text displayed on WinForm element

Time:08-06

So I currently have a working C# console application that I am working on converting to a WinForm app. I am trying to write a function so I can essentially replace all my Console.WriteLine() from when it was a console app, to instead display the same text to the WinForm. I wanted to write a public function that I can call from other classes, that takes a string as an argument and will display the string on the WinForm.

Essentially my main class Program1 will run, then any time it needs to display a message it calls a method from my WinForm class Form1 to print that message to the WinForm

I am a little confused how to do this, I created a label in the WinForm design tab and have a function that was created by visual studio

    private void label1_TextChanged(object sender, EventArgs e)
        {

        }

However wouldn't this function only be called from the winform itself? How could I create the function I described above, or is this not possible using WinForms?

CodePudding user response:

That is a method signature for an event. It doesn't necessarily be TextChanged event but likely it is (it is the naming given by VS for TextChanged on control label1. If that label1 is really a Label, then TextChanged event would be useless anyway.

For displaying things on a form, probably the best control would be a textbox control with Multiline property set to true and ReadOnly to true. ie:

void Main()
{
    ShowInfo("Any information string.");
}

void ShowInfo(string information)
{
    var f = new Form();
    var t = new TextBox
    {
        Dock = DockStyle.Fill,
        Multiline = true,
        ReadOnly = true,
        Text = information
    };
    f.Controls.Add(t);
    f.Show();
}

CodePudding user response:

Drop a label on your form. Name it UserFeedbackLabel. Set its Text property so that it's empty.

Then create a simple function like:

private void WriteUserFeedback (string message)
{
    UserFeedbackLabel.Text = message;
}

You may also want something like:

private void ClearUserFeedback()
{
    WriteUserFeedback(string.Empty);
}

Variations on the theme

If you are going to want to access this from multiple threads, then you will need to use the normal InvokeRequired / Invoke dance (which you can look up).

You could also have a second parameter that indicates whether message is simply a message or an error message and change the label's ForeColor property.

Finally, if you are really keen, you could put a multiline text box on the form, and append Environment.NewLine and the message to the textbox's Text property to show all messages. (if you do that, add vertical scrollbars to the textbox, and move the selection to the end of the text box after each append)

Showing Errors

Add an enum like this to your project:

public enum MessageType
{
    Normal,
    Error,
}

Then change WriteUserFeedback to look like this:

private void WriteUserFeedback(string message, MessageType messageType = MessageType.Normal)
{
    UserFeedbackLabel.ForeColor = messageType == MessageType.Normal ? Color.Black : Color.Red;
    UserFeedbackLabel.Text = message;
}

Supporting Multiple Threads (and colors)

Here's how to use InvokeRequired and Invoke to allow this method to be called from any thread, not just the UI thread. Change WriteUserFeedback to look like:

private void WriteUserFeedback(string message, MessageType messageType = MessageType.Normal)
{
    if (InvokeRequired)
    {
        Invoke(new Action(() => WriteUserFeedback(message, messageType)));
        return;
    }
    UserFeedbackLabel.ForeColor = messageType == MessageType.Normal ? Color.Black : Color.Red;
    UserFeedbackLabel.Text = message;
}

The InvokeRequired property returns true if the code is not running on the UI thread (everything that touches your form has to run on that thread). The Invoke call calls the method recursively, after marshalling the call context to the UI thread.

CodePudding user response:

This answer provides:

[...] a public function that I can call from other classes, that takes a string as an argument and will display the string on the WinForm.

The name of this new function is Console.WriteLine() just like the old one so that your code from the Console app can be copy pasted as is without changing anything. Then, following the example below, the result will be that your string will be displayed on the Winform. But how do we keep the compiler from being confused when there are two versions of Console.WriteLine available? We simply add a using declaration at the top of every file that uses Console.WriteLine. The compiler uses this to disambiguate Console and now chooses our version of Console.WriteLine that makes the text go to the Winform.

using System;
using System.IV;
using Console = System.IV.Console; // Set one time for this one file.
using System.Windows.Forms;

Now, instead of having to essentially replace all my Console.WriteLine() from when it was a Console app statements, you can simply copy-paste them and use them just the way they are. This outcome is made possible by writing our own Console class in the System.IV namespace. This Console class has a WriteLine method that fires a static event named ConsoleWrite whenever it's called by any of your classes. It is by subscribing to this static event that you can achieve the outcome you describe, where the string will be displayed on the Winform (in this example it goes to a multiline TextBox named textBoxEcho).

Simple example: Entering text in the Send box calls the new version of Console.WriteLine in the Winforms app. So this is how you could create the function you described above. This screenshot demonstrates that it behaves the way you said you wanted it to.

explanation


This is a new class named Console:

namespace System.IV
{
    static class Console
    {
        public static event ConsoleWriteEventHandler ConsoleWrite;
        public static void Write(object o) =>
            Console.ConsoleWrite?.Invoke(new ConsoleWriteEventArgs(o.ToString(), newLine: false));

        public static void WriteLine(object o) =>
            Console.ConsoleWrite?.Invoke(new ConsoleWriteEventArgs(o.ToString(), newLine: true));
    }

    public delegate void ConsoleWriteEventHandler(ConsoleWriteEventArgs e);
    public class ConsoleWriteEventArgs : EventArgs
    {
        public string Text { get; }
        public bool NewLine { get; }
        public ConsoleWriteEventArgs(string text, bool newLine)
        {
            Text = text;
            NewLine = newLine;
        }
    }
}

Winform code that uses the custom Console.WriteLine method.

using System;
using System.IV;
using Console = System.IV.Console; // Tells compiler to use our version file.
using System.Windows.Forms;

namespace winforms_with_console_writeline
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            textBoxSend.KeyDown  = onKeyDown;
            // Subscribe here to the static event so we can
            // do the echo when 'Console.WriteLine' is invoked.
            Console.ConsoleWrite  = onConsoleWrite;
        }
        private void onKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Return)
            {
                e.Handled = e.SuppressKeyPress = true;

                // ================================
                // Here is the actual call in Winforms. Usage is no different.
                Console.WriteLine(textBoxSend.Text);
                // ================================

                BeginInvoke((MethodInvoker)delegate { textBoxSend.Clear();  });
            }
        }
        // Here is where you can write the text wherever you want to put it.
        // I used 'textBoxEcho' but you might want to use 'label' instead.
        private void onConsoleWrite(ConsoleWriteEventArgs e)
        {
            var text = e.NewLine ? $"{e.Text}{Environment.NewLine}" : e.Text;
            if(InvokeRequired) Invoke((MethodInvoker)delegate { textBoxEcho.AppendText(text); });
            else textBoxEcho.AppendText(text);
        }
    }
}
  • Related