Home > other >  C# How can I pass a UI control reference to a class module "generically" and access it
C# How can I pass a UI control reference to a class module "generically" and access it

Time:04-25

I have done a fair amount of C# programming, but it's been all very small stuff, and coming from a C background (not C ) in the embedded space I haven't fully embraced the OO approach, and I'm happy to say I'm trying to change that.

I'm rewriting some serial comm code I've used over and over into a Class Module that I can just drop into future projects and instantiate. I have everything working except one thing: a logging function where I write the com characters to a textbox and the indices from a ring buffer into labels. I can make that all work, actually, but I was hoping to "generalize" more and pass one of any number of things with a ".Text" property for the ring buffer indices (for example, a Label, a TextBox, or a ToolStripStatusLabel).

Here’s the example, say I have a form with a text box, a label, and a ToolStripStatusLabel. The GUI is on one thread and my class module is running on another one (mostly because it is dealing with the serial port, which is perhaps inconsequential to the question?)

Anyway, lets say I have a modular variable in my class (declared as “Object”?) and I want to create a method in the object to pass in a refence to any one of those three UI elements, each one of which has the “.Text” property to which I can write.

The Class module has a delegate to invoke that will allow it to write to another gui element on the form called txtLog which is visually logging the data. At the same time I want to write something to this other passed-in UI object (say I want to display the index variable from the ring buffer).

It works fine if I stick to a Label (or any one of them) and declare everything as a Label:

===================

Up at the top, the modular variable to hold the control reference:

System.Windows.Forms.Label inPtrLbl;

And then a method to pass the assignment into the class:

public void TurnOnLogging(System.Windows.Forms.TextBox location, System.Windows.Forms.Label inLbl, System.Windows.Forms.Label outLbl)
{
    comLogging = true;
    logBox = location;
    inPtrLbl= new System.Windows.Forms.Label();
    inPtrLbl = inLbl;
}

Because the class and the form are on different threads, you need to use the Invoke stuff:

private delegate void UpdateUiTextDelegate(byte text, Int32 ptr);

“Receive” which runs for the event that fires when a char is received looks like this (“esLink” is what I named my serial port inside this class) and you can see the Invoke of “WriteData” happening to write the char into the textbox’s .Text property, which also “grants the right” (I know that’s’ the wrong thing to say) to write the text into the label on the same UI thread in the “WriteData” function below it:

private void Recieve(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    byte recieved_data;

    // Collecting the characters received to our 'buffer' (string).
    while (esLink.BytesToRead > 0)
    {
        recieved_data = (byte)esLink.ReadByte();

        // add it to the circular buffer
        rxBuffer[nextBufferInputPoint] = recieved_data;
        if (  nextBufferInputPoint >= RX_BUFFER_SIZE)
            nextBufferInputPoint = 0;

        // then put it in the text box for logging
        logBox.Invoke(new UpdateUiTextDelegate(WriteData), recieved_data, nextBufferInputPoint);
    }
}

private void WriteData(byte text, Int32 ptr)
{
    // Assign the value of the recieved_data to the TextBox and label.
    if (comLogging)
    {
        logBox.Text  = (char)text;
        inPtrLbl.Text = ptr.ToString();
    }
}

So, all this works like a charm, really. As long as I declare the variable in the class to be the same type as what I’m passing in. But I want to pass (almost) anything with a .Text property to it so I have more flexibility in designing my GUI. I tried declaring the passed item as an Object, it gets there but the IDE complains that the object doesn’t have a .Text property. I tried declaring it as something with a .Text property and then “changing” it with a “new” but that didn’t work either. I said, ok, I’ll limit it to three types and create overloaded methods for the three types. The problem there is I could only make that work if I declared the three different types at the top and only used one (and set some kind of control variable to decide which one to use when writing to the UI control).

I’m thinking there has to be an easier way. In principle, I want to declare a generic object that I can turn into anything based on what I pass in and access its .Text property. At the very least, creating an overloaded method for each type (realistically there might be 4 or 5 different types only) would be acceptable (but not ideal) and I could live with that.

(I hope I have explained this well, sorry if not...)

Thanks,

-Vin

CodePudding user response:

You may define the type (of the parameter?) as "Control" (System.Windows.Forms.Control), as most UI control classes are derived from this class. Actually, the Control class has really a large number of properties, such as "Text", "Location", "Size", "Parent", etc.

See https://docs.microsoft.com/dotnet/api/system.windows.forms.control

CodePudding user response:

Pass a delegate instead.

Action<char> _logHandler;

public void TurnOnLogging(Action<char> logHandler)
{
    comLogging = true;
    _logHandler = logHandler;
}

Then, when you have data to log, call the delegate.

private void WriteData(byte text, Int32 ptr)
{
    // Assign the value of the recieved_data to the TextBox and label.
    if (comLogging)
    {
        _logHandler((char)text);
    }
}

This way the caller can decide how the contents are displayed. They might not want to use a textbox, or a winforms control at all, but maybe log it to a file.

obj.TurnOnLogging( x => TextBox1.Text  = x.ToString() );

Or

obj.TurnOnLogging( x => Label1.Text  = x.ToString() );

Or

obj.TurnOnLogging( x => _logger.Write(x) );

You might also consider getting rid of your unusual mechanism in favor of something more idiomatic, such as a custom event.

  • Related