RichTextBox 多线颜色(仅限部分线颜色)

RichTextBox Multi-Line Color (Partial Line Color Only)

我在 RichTextBox 控件中保留多行颜色时遇到问题。

问题:当向RichTextBox控件追加新消息时,需要给消息涂上某种颜色,新消息之前的所有消息都涂成灰色并保持这种状态。

问题:如何在追加的时候只绘制需要绘制的信息

我有一个从后台线程触发的状态更新事件,该事件将更新的状态和消息添加到 List<StatusEventArgs>。然后,我清除 RichTextBox 控件并开始向其添加更新。我可能应该将其移动到仅附加文本而不是每次都清除它并重建它;但是,我这样做是为了保留颜色。这些消息是一种向用户表示的日志形式,因此他们知道在这个漫长的 运行 过程中当前发生了什么。例如:

1. Running process.
2. Starting something.
3. Doing something else.
4. Doing something. SUCCESS.
5. Doing something. SUCCESS.
6. Doing something. SUCCESS.
7. Doing something. FAIL. Attempting to continue.
8. Doing some other thing.
9. Complete.

在上面的示例中,第 4 - 7 行有额外的附加文本,例如 SUCCESS。这些行的开头应该是 RichTextBox 控件的正常 Color.Black 颜色。附加消息 SUCCESS 应根据为该消息提供的 Status 着色。因此,例如,第 4 行和第 6 行显示 SUCCESS 并以成功状态接收。第 5 行表示成功,但遇到警告或小问题并收到警告状态。收到第 7 行错误状态。正如您将在下面提供的代码中看到的那样,每个状态都有自己的颜色与之关联。同一行上该状态之前的文本应保持 Color.Black 颜色。

重要提示

消息和它的状态是分开发送的,这是因为每个操作花费的时间不同,因此用户应该知道一个进程正在发生,到目前为止还没有来自该进程的状态报告.

当前代码

private List<StatusEventArgs> events = new List<StatusEventArgs>();
private void StatusUpdate(object sender, StatusEventArgs e) {
    events.Add(e);
    rtb.Text = string.Empty;

    foreach (StatusEventArgs sea in events) {
        Status s = sea.Status;
        rtb.SelectionStart = rtbConsole.TextLength;
        rtb.SelectionLength = 0;
        rtb.SelectionColor = rtbConsole.ForeColor;

        if (s == UpdateStatus.Error || s == UpdateStatus.Warning || s == UpdateStatus.Success) {
            rtb.Text = rtb.Text.TrimEnd('\n');
            rtb.SelectionStart = rtbConsole.TextLength;
            rtb.SelectionLength = 0;

            switch (s) {
                case Status.Error: rtb.SelectionColor = Color.DarkRed; break;
                case Status.Warning: rtb.SelectionColor = Color.DarkGoldenrod; break;
                case Status.Success: rtb.SelectionColor = Color.DarkGreen; break;
            }
        }
        rtb.AppendText($"{sea.Message}\n");
    }
    rtb.ScrollToCaret();
}

依赖关系

public enum UpdateStatus { NoOperation, Error, Warning, Success }
public class StatusEventArgs {
    public UpdateStatus Status { get; set; }
    public string Message { get; set; }
}

我在 Whosebug 上查看了几个相关问题,(this one) 最接近我的需要;但是,post 建议选择特定范围内的文本,但仍然无效。它所做的事情与我当前的代码所做的完全相同。请记住,我在实现答案时没有循环或事件列表。我也不能利用建议在 RichTextBox 中绘制特定文本,因为正如我在上面的示例中所述,SUCCESS 消息可以有两种不同的颜色,具体取决于是否收到警告。

尝试码

int previousLength = rtb.TextLength;
UpdateStatus s = e.Status;
bool status = s == UpdateStatus.Error || s == UpdateStatus.Warning || s == UpdateStatus.Success;
if (status)
    rtb.Text = rtb.Text.TrimEnd('\n');
rtb.AppendText($"{e.Message}\n");
rtb.ScrollToCaret();

if (status) {
    rtb.Select(previousLength, rtb.TextLength);
    switch (s) {
        case Status.Error: rtb.SelectionColor = Color.DarkRed; break;
        case Status.Warning: rtb.SelectionColor = Color.DarkGoldenrod; break;
        case Status.Success: rtb.SelectionColor = Color.DarkGreen; break;
    }
    rtb.Select(0, 0);
}

老实说,我觉得我只是错过了一步,或者需要做一些不同的事情。每次收到新的状态消息时都重新着色所有状态消息对于这样一项微不足道的任务来说似乎有点过分了。

更新

我测试了 Dictionary<int[], UpdateStatus> 方法,它按照我需要的方式工作;但是,我认为对于如此简单的事情来说,这有点过头了:

private Dictionary<int[], UpdateStatus> selections = new Dictionary<int[], UpdateStatus>();
private void StatusUpdate(object sender, StatusEventArgs e) {
    int previousLength = rtbConsole.TextLength;
    UpdateStatus s = e.Status;
    bool status = s != UpdateStatus.NoOperation;
    if (status)
        rtb.Text = rtb.Text.TrimEnd('\n');
    rtb.AppendText($"{e.Message}\n");
    rtb.ScrollToCaret();
    if (status)
        selections.Add(new int[] { previousLength, rtb.TextLength }, s);

    // Set all basic text to black.
    rtb.Select(0, rtb.TextLength);
    rtb.SelectionColor = Color.Black;

    // Color all status messages.
    foreach (int[] selection in selections.Keys) {
        rtb.Select(selection[0], selection[1]);
        switch (selections[selection])
            case Status.Error: rtb.SelectionColor = Color.DarkRed; break;
            case Status.Warning: rtb.SelectionColor = Color.DarkGoldenrod; break;
            case Status.Success: rtb.SelectionColor = Color.DarkGreen; break;
        }

        // Prevent messages in-between status messages from being colored.
        rtb.Select(selection[1], rtb.TextLength);
        rtb.SelectionColor = Color.Black;
    }
}

更新 2

下面是我对 LarsTech post 的实现。它仍然将当前状态消息之前的所有内容都涂成黑色:

UpdateStatus s = e.Status;
if (s != UpdateStatus.NoOperation)
    rtb.Text = rtb.Text.TrimEnd('\n');
Color textColor = Color.Black;
switch (selections[selection]) {
    case Status.Error: textColor = Color.DarkRed; break;
    case Status.Warning: textColor = Color.DarkGoldenrod; break;
    case Status.Success: textColor = Color.DarkGreen; break;
}
rtb.Select(rtb.TextLength, 0);
rtb.SelectionColor = textColor;
rtb.AppendText($"{e.Message}\n");
rtb.ScrollToCaret();

更新 3

所以上面的 Update 2 方法有效,问题是以下代码行:

rtb.Text = rtb.Text.TrimEnd('\n');

这行代码导致整个控件删除所有当前格式这让我想知道,如果我想保留我已经拥有的格式,我应该使用 Rtf 属性 吗?我想我会尝试一下并找出答案。 Per MSDN:

The Text property does not return any information about the formatting applied to the contents of the RichTextBox. To get the rich text formatting (RTF) codes, use the Rtf property. The amount of text that can be entered in the RichTextBox control is limited only by available system memory.

最终更新

Rtf 属性 在我的情况下不起作用(只是将 rtb.Text 换成 rtb.Rtf。尝试了其他几种方法,但 none 起作用了。但是,对于那些(像我一样)传递直接消息并在打印时附加新行的人,您可以采用前缀新行的方法。然后你可以添加一些逻辑来防止它在不应该的时候发生那里。这消除了对 TrimEnd 的需要,因此接受的答案将正常工作:

// Field
bool firstUpdate = true;

private void StatusUpdate(...) {
    UpdateStatus s = e.Status;

    Color textColor = Color.Black;
    switch (selections[selection]) {
        case Status.Error: textColor = Color.DarkRed; break;
        case Status.Warning: textColor = Color.DarkGoldenrod; break;
        case Status.Success: textColor = Color.DarkGreen; break;
    }
    string newline = firstUpdate || s != Status.NoOperation ? string.Empty : "\n";
    rtb.Select(rtb.TextLength, 0);
    rtb.SelectionColor = textColor;
    rtb.AppendText($"{newline}{e.Message}");
    rtb.ScrollToCaret();

    if (firstUpdate)
        firstUpdate = false;
}

问题是由于替换文本中的字符串引起的属性:

rtb.Text = rtb.Text.TrimEnd('\n');

这行代码导致整个控件删除所有当前格式Per MSDN:

The Text property does not return any information about the formatting applied to the contents of the RichTextBox. To get the rich text formatting (RTF) codes, use the Rtf property. The amount of text that can be entered in the RichTextBox control is limited only by available system memory.

否则,代码可以简化为:

Color textColor = Color.Black;
switch (e.Status) {
    case Status.Error: textColor = Color.DarkRed; break;
    case Status.Warning: textColor = Color.DarkGoldenrod; break;
    case Status.Success: textColor = Color.DarkGreen; break;
}
rtb.Select(rtb.TextLength, 0);
rtb.SelectionColor = textColor;
rtb.AppendText($"{e.Message}\n");
rtb.ScrollToCaret();