我创建了一个记录器模块文件类,并在类中对其进行了扩展,并添加了我自己的自定义StringBuilder.我希望能够根据Logger类中每StringBuilder行的 colored颜色 设置,轻松地为Form1设计器中的richTextBox1控件中的行着色.

在类Logger中,我扩展了类以使用我自己的带有 colored颜色 属性的定制StringBuilder.

// Logger.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Logger_Testing
{
    public class Logger
    {
        private RichTextBox richTextBoxLogger;
        private CustomStringBuilder sb = new CustomStringBuilder();

        public Logger(RichTextBox richTextBoxLogger)
        {
            this.richTextBoxLogger = richTextBoxLogger;
        }

        public void LogStartDownload(string fileName, string fileUrl)
        {
            sb.AppendLine($"[Start Time]: {DateTime.Now}", Color.Yellow);
            sb.AppendLine($"[File Name]: {fileName}", Color.Yellow);
            sb.AppendLine($"[File URL]: {fileUrl}", Color.Yellow);
            sb.AppendLine($"[Status] Downloading in progress", Color.Yellow);
        }

        public void LogDownloadProgress(double percentage, double speed, long totalBytes, long contentLength, TimeSpan elapsedTime)
        {
            this.elapsedTime = elapsedTime; // Store elapsed time for later use
            sb.AppendLine($"[Progress]: {percentage}%", Color.Yellow);
            sb.AppendLine($"[Downloaded]: {FormatBytes(totalBytes)} / {FormatBytes(contentLength)}", Color.Yellow);
            sb.AppendLine($"[Download Speed]: {FormatBytes(speed)}/s", Color.Yellow);
            sb.AppendLine($"[Time Elapsed]: {FormatTimeSpan(elapsedTime)}", Color.Yellow);
        }

        public void LogDownloadCompleted(bool isSuccess, TimeSpan elapsedTime, bool isCancelled)
        {
            this.elapsedTime = elapsedTime; // Update elapsed time when the download completes

            if (!isCancelled)
            {
                sb.Replace($"[Status] Downloading in progress", $"[Status]: Download {(isSuccess ? "Completed Successfully" : "Failed")}", Color.Yellow);
            }
            else
            {
                sb.Replace($"[Status] Downloading in progress", $"[Status]: Download Cancelled", Color.Red);
            }

            sb.AppendLine($"[Elapsed Time]: {FormatTimeSpan(elapsedTime)}", Color.Yellow);
            sb.AppendLine($"[End Time]: {DateTime.Now}", Color.Yellow);
            sb.AppendLine("", Color.Yellow); // Add a space line between download logs
        }

        public void LogDownloadCompleted()
        {
            sb.AppendLine("[Status]: All downloads completed", Color.Yellow);
            sb.AppendLine("", Color.Yellow);
        }

        public void LogDownloadCancelled()
        {
            sb.AppendLine("[Status]: Operation cancelled and all file have been deleted", Color.Yellow);
            sb.AppendLine("", Color.Yellow);
        }

        public string GetLog(RichTextBox richTextBoxLogger)
        {
            richTextBoxLogger.Clear();
            string log = sb.ToString();
            richTextBoxLogger.AppendText(log); // Set RichTextBox text as RTF to preserve formatting
            //logBuilder.Clear(); // Clear the log after appending it once

            return log;
        }

        private string FormatBytes(double bytes)
        {
            const int scale = 1024;
            string[] units = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };

            if (bytes <= 0)
            {
                return "0 B";
            }

            int magnitude = (int)Math.Floor(Math.Log(bytes, scale));
            int index = Math.Min(magnitude, units.Length - 1);

            double adjustedSize = bytes / Math.Pow(scale, index);
            string format = (index >= 0 && index < 3) ? "F2" : "F0";

            return $"{adjustedSize.ToString(format)} {units[index]}";
        }

        private string FormatTimeSpan(TimeSpan timeSpan)
        {
            return $"{timeSpan.Hours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
        }

        public void Replace(string oldText, string newText, Color textColor)
        {
            sb.Replace(oldText, newText, textColor);
            UpdateRichTextBox();
        }

        private void UpdateRichTextBox()
        {
            richTextBoxLogger.Clear();

            foreach (var line in sb.Lines)
            {
                richTextBoxLogger.SelectionStart = richTextBoxLogger.TextLength;
                richTextBoxLogger.SelectionLength = 0;
                richTextBoxLogger.SelectionColor = line.Color;
                richTextBoxLogger.AppendText(line.Text + "\n");
                richTextBoxLogger.SelectionColor = richTextBoxLogger.ForeColor; // Reset color
            }
        }

        public class CustomStringBuilder
        {
            public List<CustomStringBuilderItem> Lines { get; } = new List<CustomStringBuilderItem>();

            public void AppendLine(string text, Color color)
            {
                Lines.Add(new CustomStringBuilderItem { Text = text, Color = color });
            }

            public void Replace(string oldText, string newText, Color textColor)
            {
                var line = Lines.Find(l => l.Text.Contains(oldText));

                if (line != null)
                {
                    line.Text = line.Text.Replace(oldText, newText);
                    line.Color = textColor;
                }
            }

            public override string ToString()
            {
                StringBuilder result = new StringBuilder();

                foreach (var line in Lines)
                {
                    result.AppendLine(line.Text);
                }

                return result.ToString();
            }
        }

        public class CustomStringBuilderItem
        {
            public string Text { get; set; }
            public Color Color { get; set; }
        }
    }
}

例如,我希望在Form1设计器中的richTextBox1中将该行显示为红色:

sb.Replace($"[Status] Downloading in progress", $"[Status]: Download Cancelled", Color.Red);

这是我在表格1中使用它的方式

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Logger_Testing
{
    public partial class Form1 : Form
    {
        Logger logger;
        private TimeSpan _elapsedTime;

        public Form1()
        {
            InitializeComponent();

            logger = new Logger(richTextBox1);

            logger.LogStartDownload("Hello", "Hello World");
            logger.GetLog(richTextBox1);
        }

        private void btnReplace_Click(object sender, EventArgs e)
        {
            logger.LogDownloadCompleted(false, _elapsedTime, true);
            logger.LogDownloadCancelled();

            logger.GetLog(richTextBox1);
        }
    }
}

但结果是,当我单击按钮替换文本时,替换的文本也是黄色的,就像richTextBox1中的所有其余文本一样,而不是特定替换行的红色.

行"[状态]:下载已取消"应为红色.

All the text is in yellow but the line [Status]: Download Cancelled should be colored in red

推荐答案

你没有注意到的是,你的none%的色彩逻辑是有效的.如果您将富文本框上的默认forecolor设置为Black(或黄色以外的任何 colored颜色 ),或者像我所做的那样将其保留为默认状态,则您将根本看不到任何黄色文本.

The first step to debugging this would be to put a break point in the UpdateRichTextBox() method: Setting break point in UpdateRichTextBox when you run the app, this method is never entered, we follow the references back a step, the replace method before it is the only method that calls UpdateRichTextBox but it is never called itself:

Highlight references to Replace

我怀疑您的意思是用此方法替换sb.Replace()的所有其他引用.但这是不必要地 for each 更改重写富文本框,而您实际上只需要在每个批量操作集结束时执行此操作.

因此,我建议您删除此方法:

   public void Replace(string oldText, string newText, Color textColor)
   {
       sb.Replace(oldText, newText, textColor);
       UpdateRichTextBox();
   }

取而代之的是,在操作字符串构建器的每个方法的末尾调用UpdateRichTextBox(),按照惯例,在所有操作之后已经调用了GetLog,所以我们只需要将这个方法调用放在那里...

Convention of calling GetLog

这突出了这个逻辑中的另一个缺陷,即使我们以其他方式修复之前的错误也无关紧要,每次您拨打GetLog(),它当前都会清除富文本框.

这一行的 comments 是正确的陈述:

richTextBoxLogger.AppendText(log); // Set RichTextBox text as RTF to preserve formatting

但这取决于您首先提供给富文本框的RTF(富文本格式)的字符串.然而,您的CustomStringBuilder的输出是纯文本,根本不包含任何RTF标记格式,稍后将对此进行详细介绍.

因此,这个快速解决方案是认识到GetLogUpdateRichTextBox在概念上总是意味着做同样的事情,GetLog是你最初的try ,需要传入对富文本编辑器的引用,UpdateRichTextBox是你的新try ,使用对富文本框的内部引用,只是还没有完全连接.快速解决方法是合并这两个方法:

/// <summary>
/// Clears the <paramref name="richTextBoxLogger"/> and appends the with the
/// contents of the internal <see cref="CustomStringBuilder"/>
/// </summary>
/// <param name="richTextBoxLogger">
/// RichTextBox to write to, if null, will default to the 
/// RichTextBox that was provided in the constructor to this <seealso cref="Logger"/>
/// </param>
public void UpdateRichTextBox(RichTextBox richTextBoxLogger)
{
    // I would choose either member reference or local reference, doesn't add much value to support both.
    // My personal vote would be to NOT have a member reference, unless this method is on the Form class.
    if (richTextBoxLogger == null) richTextBoxLogger = this.richTextBoxLogger;

    richTextBoxLogger.Clear();

    foreach (var line in sb.Lines)
    {
        richTextBoxLogger.SelectionStart = richTextBoxLogger.TextLength;
        richTextBoxLogger.SelectionLength = 0;
        richTextBoxLogger.SelectionColor = line.Color;
        richTextBoxLogger.AppendText(line.Text + "\n");
        richTextBoxLogger.SelectionColor = richTextBoxLogger.ForeColor; // Reset color
    }
}

您可以在代码中看到我的注释,将文本框传递给该类的构造函数没有太大的价值,除非当字符串构建器更改时,它将自动刷新文本框.但这一简单的更改和更新Form类中的引用将使您的用户界面正常工作:

    public Form1()
    {
        InitializeComponent();

        logger = new Logger(richTextBox1);

        logger.LogStartDownload("Hello", "Hello World");
        logger.UpdateRichTextBox(richTextBox1);
    }

    private void btnReplace_Click(object sender, EventArgs e)
    {
        logger.LogDownloadCompleted(false, _elapsedTime, true);
        logger.LogDownloadCancelled();

        logger.UpdateRichTextBox(richTextBox1);
    }

proof is in the pudding

但这是你想要的吗?可能不是,您可能希望将对UpdateRichTextBox的调用添加到其他每个公共记录器方法中.我不想鼓励这一点,但欢迎你自己go 做.我不鼓励这样做的原因是,它进一步将日志(log)记录和呈现的概念与您的特定用户界面结合在一起.这只是一个很难养成的坏习惯.

那么,什么是RTF,如何使用它呢?

我很高兴你这么问.RTF是一种纯文本标记语言,类似于HTML和GitHub Markdown,它具有特定的语法来包含可用于在呈现文本时格式化文本的元数据.

例如,这是在单击按钮后捕获的,格式为RTF.(我们可以从Rich Text控件本身访问:Console.WriteLine(richTextBox1.Rtf);

{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang2057{\fonttbl{\f0\fnil Segoe UI;}}
{\colortbl ;\red255\green255\blue0;\red255\green0\blue0;\red253\green245\blue230;}
{\*\generator Riched20 10.0.22621}\viewkind4\uc1 
\pard\cf1\f0\fs18 [Start Time]: 31/12/2023 1:11:03 PM\par
[File Name]: Hello\par
[File URL]: Hello World\par
\cf2 [Status]: Download Cancelled\par
\cf1 [Elapsed Time]: 00:00:00\par
[End Time]: 31/12/2023 1:11:06 PM\par
\par
[Status]: Operation cancelled and all file have been deleted\par
\par
\cf3\par
}

我不会深入讨论太多细节,如果您感兴趣的话请阅读本文:RTF Cookbook,但是您可能从中可以看到RTF与具有styles的HTML有相似之处.就像HTML一样,在RTF中can指定内联样式引用,但是RichTextBox将字体和 colored颜色 分组到引用表中,并使用速记语法来引用需要它的行的样式.

回到你的 comments :

richTextBoxLogger.AppendText(log); // Set RichTextBox text as RTF to preserve formatting

如果您的log包含类似上面的标记,那么AppendText将保留该标记,您还可以通过RichTextBox.Rtf属性设置文本,这将保留任何RTF,与清除文本框并附加RTF标记相同.

相反,当您设置RichTextbox.Text时,文本是encoded,因此任何RTF语法都是escaped.

这意味着您可以使用类似的解决方案one I proposed in your previous question来将日志(log)消息的构建与对用户控件的引用解耦.或者你已经扩展了String Builder,让我们把它添加到它,以支持基本的RTF,HTML和GitHub Markdown.你为什么要费心呢?通常你不会,因为这些类型的配置已经存在于.Net框架和现有的第三方库中,但这是一个有趣的学习实验,关于为什么你要以这种方式抽象逻辑,以及如何创建一个可能在其他项目中对你有用的组件,这是X10开发人员的目标;)

这是新的ColoredStringBuilder

public class ColoredStringBuilderItem
{
    public string Text { get; set; }
    public Color Color { get; set; }
}

public class ColoredStringBuilder
{
    public List<ColoredStringBuilderItem> Lines { get; } = new List<ColoredStringBuilderItem>();

    public void AppendLine(string text, Color color)
    {
        Lines.Add(new ColoredStringBuilderItem { Text = text, Color = color });
    }

    public void Replace(string oldText, string newText, Color textColor)
    {
        var line = Lines.Find(l => l.Text.Contains(oldText));

        if (line != null)
        {
            line.Text = line.Text.Replace(oldText, newText);
            line.Color = textColor;
        }
    }
    public override string ToString()
    {
        return ToString(TextFormat.Plain);
    }

    public string ToString(TextFormat format)
    {
        StringBuilder result = new StringBuilder();
        // build the color tables, we can use this in RTF and HTML
        List<string> colorDefinitions = new List<string>();
        Dictionary<Color, string> colorReferenceMap = new Dictionary<Color, string>();
        var colors = Lines.GroupBy(x => x.Color).OrderByDescending(x => x.Count()).Select(x => x.Key).ToList();
        foreach (var color in colors)
        {
            switch (format)
            {
                case TextFormat.RTF:
                    string reference = $"\\cf{colorReferenceMap.Count + 1}";
                    colorReferenceMap.Add(color, reference);
                    colorDefinitions.Add($"\\red{color.R}\\green{color.G}\\blue{color.B}");
                    break;

                case TextFormat.HTML:
                    string className = $"cf{colorReferenceMap.Count + 1}";
                    colorReferenceMap.Add(color, className);
                    colorDefinitions.Add($".{className} {{ color: #{color.R:x2}{color.G:x2}{color.B:x2}; }}");
                    break;

                case TextFormat.Markdown:
                    // make Red use block quote?
                    if (color == Color.Red)
                        colorReferenceMap.Add(color, "> ");
                    else
                        colorReferenceMap.Add(color, "");
                    break;
            }
        }

        // inject the styles
        switch (format)
        {
            case TextFormat.RTF:
                // preamble
                result.AppendLine("{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang2057{\\fonttbl{\\f0\\fnil Segoe UI;}}");
                result.AppendLine($"{{\\colortbl;{String.Join(";", colorDefinitions)};}}");
                result.AppendLine(@"{\*\generator ColoredStringBuilder 1.0.0}\viewkind4\uc1");
                break;
            case TextFormat.HTML:
                    result.Append("<html><head>");
                result.AppendLine("<style>");
                foreach (var style in colorDefinitions)
                    result.AppendLine(style);
                result.AppendLine("</style>");
                result.AppendLine("</head>");
                result.AppendLine("<body>");
                break;
        }

        foreach (var line in Lines)
        {
            switch (format)
            {
                case TextFormat.Plain:
                    result.AppendLine(line.Text);
                    break;
                case TextFormat.RTF:
                    result.Append(colorReferenceMap[line.Color]);
                    result.Append(line.Text);
                    result.Append("\\par");
                    result.AppendLine();
                    break;
                case TextFormat.HTML:
                    result.Append($"<span class=\"{colorReferenceMap[line.Color]}\">");
                    result.Append(line.Text);
                    result.Append("</span></br>");
                    result.AppendLine();
                    break;
                case TextFormat.Markdown:
                    result.Append(colorReferenceMap[line.Color]);
                    result.Append(line.Text);
                    result.AppendLine("  "); // creates a single line break in the current paragraph
                    break;
                default:
                    throw new NotImplementedException($"Have not yet implemented support for format: {format}");
            }
        }

        // close the document
        switch (format)
        {
            case TextFormat.RTF:
                // preamble
                result.AppendLine(@"}");
                break;
            case TextFormat.HTML:
                result.AppendLine("</body>");
                result.AppendLine("</html>");
                break;
        }


        return result.ToString();
    }

}

public enum TextFormat
{
    Plain,
    RTF,
    Markdown,
    HTML
}

GetLog现在只需调用字符串构建器并传递格式

/// <summary>
/// Returns the contents of the internal <see cref="CustomStringBuilder"/>
/// formatted int the requested markdown <paramref name="format"/>
/// </summary>
/// <param name="format">The markdown format for the log</param>
public string GetLog(TextFormat format = TextFormat.Plain)
{
    return sb.ToString(format);
}

Form1现在看起来像这样,请注意,我们根本不需要传递对文本框的引用.

public partial class Form1 : Form
{
    Logger logger;
    private TimeSpan _elapsedTime;
    public Form1()
    {
        InitializeComponent();

        logger = new Logger(richTextBox1);

        logger.LogStartDownload("Hello", "Hello World");
        richTextBox1.Rtf = logger.GetLog(Logger.TextFormat.RTF);
    }

    private void btnReplace_Click(object sender, EventArgs e)
    {
        logger.LogDownloadCompleted(false, _elapsedTime, true);
        logger.LogDownloadCancelled();

        richTextBox1.Rtf = logger.GetLog(Logger.TextFormat.RTF);

        Debug.WriteLine(logger.GetLog(Logger.TextFormat.HTML));
        Debug.WriteLine(logger.GetLog(Logger.TextFormat.Markdown));
    }
}

您可以在调试器中看到HTML和Markdown输出,以获得一些乐趣.)


我可以看到您将根据这段代码多问几个问题,作为单独的问题发布是好的,但为了最大限度地利用这个平台,您应该将您的代码示例减少到最低限度.

有这么多代码的问题很难让更广泛的社区联系起来,特别是当标题与您遇到的实际问题没有真正相关时.在这种情况下,您编写的代码可以工作,但从未被调用.如果你花时间写一个[MRE],你可能会发现这一点,并解决了自己的问题.

我试图以一种建设性和相关性的方式来回答这个问题,以帮助其他有类似问题的开发人员,但现实是这个问题是如此特定于您的代码库,以至于Stack Overflow上的其他用户根本不可能看到它,即使他们看到了它的价值,也可能看不到它的价值,或者对现在正在成为代码审查讨论的贡献……

将来,您可能会考虑在https://codereview.stackexchange.com中发表文章,以获得有关您所编写代码的样式和复杂性的指导.

Csharp相关问答推荐

我如何才能获得被嘲笑班级的私有成员?

C# uwp中的Win11启动屏幕剪辑工作方式不同

TDLib与机器人共享电话号码

为什么Blazor值在更改后没有立即呈现?

无法解析数据库上下文的服务

==和Tuple对象的相等<>

AsNoTrackingWithIdentitySolutions()似乎不起作用?

限制特定REST API不被访问,但部署代码

使用可信第三方的Iext8.Net pdf签名

为什么在使用动态obj+类obj时会调用串联?

在C#中,非静态接口方法的抽象和虚拟是冗余的吗?

Selify只更改第一个下拉菜单,然后忽略REST-C#

使用C#和.NET 7.0无法访问Cookie中的数据

为什么C#/MSBuild会自发地为不同的项目使用不同的输出路径?

我可以强制System.Text.Json.JsonSerializer以非递归方式工作吗?

基于C#方法的EF核心过滤查询(缓冲与流)

在等待OnGetAsync时打开Razor Page显示微调器

Azure队列触发器未使用隔离的工作进程执行

当要删除的子模型没有父模型的1:多属性时,如何告诉实体框架设置1:1 FK条目?

使用';UnityEngineering.Random.Range()';的IF语句仅适用于极高的最大值