【问题标题】:RichTextBox Multi-Line Color (Partial Line Color Only)RichTextBox 多行颜色(仅部分行颜色)
【发布时间】:2018-07-26 18:35:00
【问题描述】:

我在 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; }
}

我在 StackOverflow 上查看了几个相关问题,并且 (this one) 最接近我的需要;但是,该帖子建议选择特定范围内的文本,但这仍然不起作用。它和我当前的代码做的完全一样。请记住,当我实现答案时,我没有循环或事件列表。我也不能利用该建议在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&lt;int[], UpdateStatus&gt; 方法,它按我需要的方式工作;然而,我相信这对于如此简单的事情来说是非常过分的:

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 帖子的实现。它仍然将当前状态消息之前的所有内容都涂成黑色:

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:

Text 属性不返回有关应用于 RichTextBox 内容的格式设置的任何信息。要获取富文本格式 (RTF) 代码,请使用 Rtf 属性。可在 RichTextBox 控件中输入的文本数量仅受可用系统内存的限制。

最终更新

Rtf 属性在我的情况下不起作用(只需将 rtb.Text 交换为 rtb.Rtf。尝试了其他几种方法,但都没有奏效。但是,对于那些(像我一样)传递直接消息和在打印时附加一个新行,您可以采用前缀新行的方法。然后您可以添加一些逻辑来防止它不应该存在。这消除了对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;
}

【问题讨论】:

    标签: c# winforms richtextbox


    【解决方案1】:

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

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

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

    Text 属性不返回有关应用于 RichTextBox 内容的格式的任何信息。要获取富文本格式 (RTF) 代码,请使用 Rtf 属性。可在 RichTextBox 控件中输入的文本数量仅受可用系统内存的限制。

    否则,代码可以简化为:

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

    【讨论】:

    • 所以我希望我可以说它有效;但是,它没有。我删除了所有旧代码并重构为基本要素和您的实现。这与我的问题中的代码相同,只是版本更短。我将使用有效的Dictionary 实现来更新我的问题,但肯定看起来很重要。
    • @davisj1691 我会杀了那本字典。您只是将状态消息附加到 RichTextBox,因此您无需每次都清除该框并重新格式化所有内容。你的班级有信息和状态——这就是你所需要的。当弹出新消息时,使用此代码更新 RichTextBox。
    • 正如我之前所说,使用Dictionary 将是我最后的选择;但是,您提供的代码会在附加消息之前绘制所有内容。我可能只是遗漏了一些关于实施的东西吗?我基本上用你提供的东西代替了我所拥有的东西,但无济于事。
    • @davisj1691 我的代码用于一次添加一条消息。不要清除盒子。不要循环浏览以前的消息。
    • 我将使用我的代码实现来编辑我的帖子。也许这会澄清一些事情。正如我所说,它仍在将状态消息之前的所有内容着色为黑色。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 1970-01-01
    相关资源
    最近更新 更多