C# 如何将 UI 控件引用传递给 class 模块 "generically" 并访问它的属性?

C# How can I pass a UI control reference to a class module "generically" and access it' s properties?

我已经完成了大量的 C# 编程,但都是非常小的东西,并且来自嵌入式领域的 C 背景(不是 C++)space 我还没有完全接受 OO 方法,我很高兴地说我正在努力改变它。

我正在将我反复使用的一些串行通信代码重写到 Class 模块中,我可以将其放入未来的项目中并实例化。除了一件事,我的一切都在工作:一个日志记录功能,我将 com 字符写入文本框,并将环形缓冲区的索引写入标签。实际上,我可以使所有这些都起作用,但我希望更多地“概括”并传递带有“.Text”属性 的任意数量的事物之一作为环形缓冲区索引(例如,一个标签,一个TextBox,或 ToolStripStatusLabel)。

这是示例,假设我有一个带有文本框、标签和 ToolStripStatusLabel 的表单。 GUI 在一个线程上,而我的 class 模块在另一个线程上 运行 (主要是因为它正在处理串行端口,这对问题来说可能无关紧要?)

无论如何,假设我的 class 中有一个模块化变量(声明为“对象”?)并且我想在对象中创建一个方法以将引用传递给这三个中的任何一个UI 个元素,每个元素都有我可以写入的“.Text”属性。

Class 模块有一个要调用的委托,允许它写入名为 txtLog 的表单上的另一个 gui 元素,该元素以可视方式记录数据。同时我想向另一个传入的 UI 对象写入一些东西(假设我想显示环形缓冲区中的索引变量)。

如果我坚持一个标签(或其中任何一个)并将所有内容声明为标签,它就可以正常工作:

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

在顶部,保存控制引用的模变量:

System.Windows.Forms.Label inPtrLbl;

然后一个方法将赋值传递给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;
}

因为class和表单在不同的线程上,所以需要用到Invoke的东西:

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

为接收到字符时触发的事件运行的“接收”看起来像这样(“esLink”是我在此 class 中命名我的串行端口的名称),您可以看到“ WriteData” 碰巧将字符写入文本框的 .Text 属性,这也“授予权利”(我知道这是错误的说法)将文本写入同一个 [=61= 上的标签] 下面的“WriteData”函数中的线程:

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();
    }
}

所以,这一切真的很有魅力。只要我在 class 中声明变量与我传入的变量类型相同。但我想传递(几乎)任何带有 .Text 属性 的东西,所以我有更灵活地设计我的 GUI。我尝试将传递的项目声明为对象,它到达那里但 IDE 抱怨该对象没有 .Text 属性。我尝试用 .Text 属性 声明它,然后用“new”“改变”它,但这也不起作用。我说,好吧,我将它限制为三种类型,并为这三种类型创建重载方法。那里的问题是,如果我在顶部声明了三种不同的类型并且只使用了一种(并设置某种控制变量来决定在写入 UI 控件时使用哪一种),我只能使它起作用。

我认为必须有更简单的方法。原则上,我想声明一个通用对象,我可以根据传入的内容将其转换为任何内容并访问其 .Text 属性。至少,为每种类型创建一个重载方法(实际上可能只有 4 或 5 种不同的类型)是可以接受的(但不理想),我可以接受。

(我希望我解释得很好,如果没有解释...)

谢谢,

-文

您可以将(参数的?)类型定义为“控件”(System.Windows.Forms.Control),因为大多数 UI 控件 class 都源自此 class.其实Control class 有很多属性,比如"Text", "Location", "Size", "Parent"等等

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

老实说,串行端口库依赖于 UI 控件有点奇怪(请参阅 separation of concerns)。我建议您进行设置,以便调用者可以传递委托。

Action<char> _logHandler;

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

然后,当您有数据要记录时,调用委托。

private void WriteData(byte text)
{
    if (comLogging)
    {
        _logHandler((char)text);
    }
}

这样调用者可以决定内容的显示方式。他们可以使用具有 Text 属性 的控件,或者如果他们愿意,可以使用不同类型的控件。或者他们可能根本不想使用文本框或 winforms 控件,但可能会将其记录到文件中。

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

obj.TurnOnLogging( x => SomeOtherControl1.Caption += x.ToString() );

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

您也可以考虑摆脱不寻常的机制,转而采用更惯用的机制,例如 custom event.