【问题标题】:How To: Best way to draw table in console app (C#)操作方法:在控制台应用程序中绘制表格的最佳方法 (C#)
【发布时间】:2010-10-25 19:18:37
【问题描述】:

我有一个有趣的问题。 想象一下,我有很多数据以非常快的间隔发生变化。 我想在控制台应用程序中将该数据显示为表格。 f.ex:

-------------------------------------------------------------------------
|    Column 1     |    Column 2     |    Column 3     |    Column 4     |
-------------------------------------------------------------------------
|                 |                 |                 |                 |
|                 |                 |                 |                 |
|                 |                 |                 |                 |
-------------------------------------------------------------------------

如何保持快速以及如何固定列宽?我知道如何在 java 中做到这一点,但我不知道它是如何在 C# 中完成的。

【问题讨论】:

  • 如果您提供 Java 解决方案以便帮助您翻译成 C# 会怎样?但是看看 String 类的 Length/PadLeft/PadRight/...

标签: c# console drawing


【解决方案1】:

String.Format 与对齐值一起使用。

例如:

String.Format("|{0,5}|{1,5}|{2,5}|{3,5}|", arg0, arg1, arg2, arg3);

创建一个格式化的行。

【讨论】:

  • 不会打印四次相同的值吗?
  • 是的,你应该修复:String.Format("|{0,10}|{1,10}|{2,10}|{3,10}|", arg0, arg1, arg2, arg3);
  • 如果你不需要桌子的棱角,这很好用。不要忘记使用 PadRight(x) 或 PadLeft(x) 来帮助您调整间距。对于我需要的东西,我只需要一些易于阅读的东西,并且在删除 |s 之后就成功了。
  • 使用- 左对齐内容,例如:{0,-5}
  • 这比许多前面提到的库要简单和好得多,特别是如果这是一个渐进式日志输出到控制台,因为上面的许多库需要一个完整的数据结构,以便将所有内容包含在一个有边框的表格中,并且全部。对于 C# 字符串插值,使用$"{interpolated,n:f}",其中n 是您的可选对齐方式(如上),f 是您的可选格式字符串。
【解决方案2】:

您可以执行以下操作:

static int tableWidth = 73;

static void Main(string[] args)
{
    Console.Clear();
    PrintLine();
    PrintRow("Column 1", "Column 2", "Column 3", "Column 4");
    PrintLine();
    PrintRow("", "", "", "");
    PrintRow("", "", "", "");
    PrintLine();
    Console.ReadLine();
}

static void PrintLine()
{
    Console.WriteLine(new string('-', tableWidth));
}

static void PrintRow(params string[] columns)
{
    int width = (tableWidth - columns.Length) / columns.Length;
    string row = "|";

    foreach (string column in columns)
    {
        row += AlignCentre(column, width) + "|";
    }

    Console.WriteLine(row);
}

static string AlignCentre(string text, int width)
{
    text = text.Length > width ? text.Substring(0, width - 3) + "..." : text;

    if (string.IsNullOrEmpty(text))
    {
        return new string(' ', width);
    }
    else
    {
        return text.PadRight(width - (width - text.Length) / 2).PadLeft(width);
    }
}

【讨论】:

    【解决方案3】:

    编辑:感谢@superlogical,您现在可以在github中找到并改进以下代码!


    我根据这里的一些想法编写了这个课程。列宽是最佳的,它可以使用这个简单的 API 处理对象数组:

    static void Main(string[] args)
    {
      IEnumerable<Tuple<int, string, string>> authors =
        new[]
        {
          Tuple.Create(1, "Isaac", "Asimov"),
          Tuple.Create(2, "Robert", "Heinlein"),
          Tuple.Create(3, "Frank", "Herbert"),
          Tuple.Create(4, "Aldous", "Huxley"),
        };
    
      Console.WriteLine(authors.ToStringTable(
        new[] {"Id", "First Name", "Surname"},
        a => a.Item1, a => a.Item2, a => a.Item3));
    
      /* Result:        
      | Id | First Name | Surname  |
      |----------------------------|
      | 1  | Isaac      | Asimov   |
      | 2  | Robert     | Heinlein |
      | 3  | Frank      | Herbert  |
      | 4  | Aldous     | Huxley   |
      */
    }
    

    这是课程:

    public static class TableParser
    {
      public static string ToStringTable<T>(
        this IEnumerable<T> values,
        string[] columnHeaders,
        params Func<T, object>[] valueSelectors)
      {
        return ToStringTable(values.ToArray(), columnHeaders, valueSelectors);
      }
    
      public static string ToStringTable<T>(
        this T[] values,
        string[] columnHeaders,
        params Func<T, object>[] valueSelectors)
      {
        Debug.Assert(columnHeaders.Length == valueSelectors.Length);
    
        var arrValues = new string[values.Length + 1, valueSelectors.Length];
    
        // Fill headers
        for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
        {
          arrValues[0, colIndex] = columnHeaders[colIndex];
        }
    
        // Fill table rows
        for (int rowIndex = 1; rowIndex < arrValues.GetLength(0); rowIndex++)
        {
          for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
          {
            arrValues[rowIndex, colIndex] = valueSelectors[colIndex]
              .Invoke(values[rowIndex - 1]).ToString();
          }
        }
    
        return ToStringTable(arrValues);
      }
    
      public static string ToStringTable(this string[,] arrValues)
      {
        int[] maxColumnsWidth = GetMaxColumnsWidth(arrValues);
        var headerSpliter = new string('-', maxColumnsWidth.Sum(i => i + 3) - 1);
    
        var sb = new StringBuilder();
        for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++)
        {
          for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
          {
            // Print cell
            string cell = arrValues[rowIndex, colIndex];
            cell = cell.PadRight(maxColumnsWidth[colIndex]);
            sb.Append(" | ");
            sb.Append(cell);
          }
    
          // Print end of line
          sb.Append(" | ");
          sb.AppendLine();
    
          // Print splitter
          if (rowIndex == 0)
          {
            sb.AppendFormat(" |{0}| ", headerSpliter);
            sb.AppendLine();
          }
        }
    
        return sb.ToString();
      }
    
      private static int[] GetMaxColumnsWidth(string[,] arrValues)
      {
        var maxColumnsWidth = new int[arrValues.GetLength(1)];
        for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
        {
          for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++)
          {
            int newLength = arrValues[rowIndex, colIndex].Length;
            int oldLength = maxColumnsWidth[colIndex];
    
            if (newLength > oldLength)
            {
              maxColumnsWidth[colIndex] = newLength;
            }
          }
        }
    
        return maxColumnsWidth;
      }
    }
    

    编辑:我添加了一个小改进——如果你希望列标题是属性名,在TableParser中添加如下方法(注意会因为反射会慢一点):

    public static string ToStringTable<T>(
        this IEnumerable<T> values,
        params Expression<Func<T, object>>[] valueSelectors)
    {
      var headers = valueSelectors.Select(func => GetProperty(func).Name).ToArray();
      var selectors = valueSelectors.Select(exp => exp.Compile()).ToArray();
      return ToStringTable(values, headers, selectors);
    }
    
    private static PropertyInfo GetProperty<T>(Expression<Func<T, object>> expresstion)
    {
      if (expresstion.Body is UnaryExpression)
      {
        if ((expresstion.Body as UnaryExpression).Operand is MemberExpression)
        {
          return ((expresstion.Body as UnaryExpression).Operand as MemberExpression).Member as PropertyInfo;
        }
      }
    
      if ((expresstion.Body is MemberExpression))
      {
        return (expresstion.Body as MemberExpression).Member as PropertyInfo;
      }
      return null;
    }
    

    【讨论】:

    • GetProperty 方法在哪里?
    • @superlogical,它已添加到答案的底部。感谢您的关注。
    • 与此处的其他答案相比,我喜欢自包含的扩展方法样式。谢谢!
    • 太棒了,我用过它,它工作得很好,只是把这个arrValues[rowIndex, colIndex] = valueSelectors[colIndex].Invoke(values[rowIndex - 1]).ToString();编辑成var val = valueSelectors[colIndex].Invoke(values[rowIndex - 1]); arrValues[rowIndex, colIndex] = val == null ? "null" : val.ToString();,这样它就显示为空了。
    • 这很好用,直到您开始使用非 Unicode 字符。然后宽度就搞砸了。
    【解决方案4】:

    有几个开源库支持控制台表格格式,范围从简单(如此处答案中的代码示例)到更高级。

    控制台表

    从 NuGet 统计数据来看,最流行的表格格式化库是 ConsoleTable。表是这样构造的(来自自述文件):

    var table = new ConsoleTable("one", "two", "three");
    table.AddRow(1, 2, 3)
         .AddRow("this line should be longer", "yes it is", "oh");
    

    可以使用其中一种预定义样式来格式化表格。它看起来像这样:

    --------------------------------------------------
    | one                        | two       | three |
    --------------------------------------------------
    | 1                          | 2         | 3     |
    --------------------------------------------------
    | this line should be longer | yes it is | oh    |
    --------------------------------------------------
    

    这个库需要没有格式的单行单元格。

    有几个基于 ConsoleTable 的库具有略微扩展的功能集,例如更多的线条样式。

    CsConsoleFormat

    如果您需要更复杂的格式,可以使用CsConsoleFormat。† 这是从流程列表(来自示例项目)生成的表格:

    new Grid { Stroke = StrokeHeader, StrokeColor = DarkGray }
        .AddColumns(
            new Column { Width = GridLength.Auto },
            new Column { Width = GridLength.Auto, MaxWidth = 20 },
            new Column { Width = GridLength.Star(1) },
            new Column { Width = GridLength.Auto }
        )
        .AddChildren(
            new Cell { Stroke = StrokeHeader, Color = White }
                .AddChildren("Id"),
            new Cell { Stroke = StrokeHeader, Color = White }
                .AddChildren("Name"),
            new Cell { Stroke = StrokeHeader, Color = White }
                .AddChildren("Main Window Title"),
            new Cell { Stroke = StrokeHeader, Color = White }
                .AddChildren("Private Memory"),
            processes.Select(process => new[] {
                new Cell { Stroke = StrokeRight }
                    .AddChildren(process.Id),
                new Cell { Stroke = StrokeRight, Color = Yellow, TextWrap = TextWrapping.NoWrap }
                    .AddChildren(process.ProcessName),
                new Cell { Stroke = StrokeRight, Color = White, TextWrap = TextWrapping.NoWrap }
                    .AddChildren(process.MainWindowTitle),
                new Cell { Stroke = LineThickness.None, Align = HorizontalAlignment.Right }
                    .AddChildren(process.PrivateMemorySize64.ToString("n0")),
            })
        )
    

    最终结果将如下所示:

    它支持任何类型的表格行(包括几个和可定制的)、带自动换行的多行单元格、颜色、基于内容或百分比增长的列、文本对齐等。

    †​​ CsConsoleFormat 是我开发的。

    【讨论】:

    • 我知道它已经很老了,但是如何创建多行单元格?我在任何地方都没有找到它
    • @Morta1 这个答案和 CsConsoleFormat 库都不是旧的。如果您使用的是 CsConsoleFormat,您可以手动插入换行符(单元格内的字符串中的“\n”),它将被正确处理,或者让库自动换行文本(这是默认设置,所以你不要添加TextWrap = TextWrapping.NoWrap 与上面的示例不同)。
    • 感谢您的快速回答,有没有使用 ConsoleTable 的方法?
    • @Morta1 不。我认为任何简单的库都不支持。
    • @HarveyDarvey 类似new Cell(text) { Color = text == "true" ? Green : Red }。如果您有许多具有相似格式规则的表格,您可以将该代码放入某个函数中,无论是单元格、行还是整个表格。
    【解决方案5】:
    class ArrayPrinter
        {
        #region Declarations
    
        static bool isLeftAligned = false;
        const string cellLeftTop = "┌";
        const string cellRightTop = "┐";
        const string cellLeftBottom = "└";
        const string cellRightBottom = "┘";
        const string cellHorizontalJointTop = "┬";
        const string cellHorizontalJointbottom = "┴";
        const string cellVerticalJointLeft = "├";
        const string cellTJoint = "┼";
        const string cellVerticalJointRight = "┤";
        const string cellHorizontalLine = "─";
        const string cellVerticalLine = "│";
    
        #endregion
    
        #region Private Methods
    
        private static int GetMaxCellWidth(string[,] arrValues)
        {
            int maxWidth = 1;
    
            for (int i = 0; i < arrValues.GetLength(0); i++)
            {
                for (int j = 0; j < arrValues.GetLength(1); j++)
                {
                    int length = arrValues[i, j].Length;
                    if (length > maxWidth)
                    {
                        maxWidth = length;
                    }
                }
            }
    
            return maxWidth;
        }
    
        private static string GetDataInTableFormat(string[,] arrValues)
        {
            string formattedString = string.Empty;
    
            if (arrValues == null)
                return formattedString;
    
            int dimension1Length = arrValues.GetLength(0);
            int dimension2Length = arrValues.GetLength(1);
    
            int maxCellWidth = GetMaxCellWidth(arrValues);
            int indentLength = (dimension2Length * maxCellWidth) + (dimension2Length - 1);
            //printing top line;
            formattedString = string.Format("{0}{1}{2}{3}", cellLeftTop, Indent(indentLength), cellRightTop, System.Environment.NewLine);
    
            for (int i = 0; i < dimension1Length; i++)
            {
                string lineWithValues = cellVerticalLine;
                string line = cellVerticalJointLeft;
                for (int j = 0; j < dimension2Length; j++)
                {
                    string value = (isLeftAligned) ? arrValues[i, j].PadRight(maxCellWidth, ' ') : arrValues[i, j].PadLeft(maxCellWidth, ' ');
                    lineWithValues += string.Format("{0}{1}", value, cellVerticalLine);
                    line += Indent(maxCellWidth);
                    if (j < (dimension2Length - 1))
                    {
                        line += cellTJoint;
                    }
                }
                line += cellVerticalJointRight;
                formattedString += string.Format("{0}{1}", lineWithValues, System.Environment.NewLine);
                if (i < (dimension1Length - 1))
                {
                    formattedString += string.Format("{0}{1}", line, System.Environment.NewLine);
                }
            }
    
            //printing bottom line
            formattedString += string.Format("{0}{1}{2}{3}", cellLeftBottom, Indent(indentLength), cellRightBottom, System.Environment.NewLine);
            return formattedString;
        }
    
        private static string Indent(int count)
        {
            return string.Empty.PadLeft(count, '─');                 
        }
    
        #endregion
    
        #region Public Methods
    
        public static void PrintToStream(string[,] arrValues, StreamWriter writer)
        {
            if (arrValues == null)
                return;
    
            if (writer == null)
                return;
    
            writer.Write(GetDataInTableFormat(arrValues));
        }
    
        public static void PrintToConsole(string[,] arrValues)
        {
            if (arrValues == null)
                return;
    
            Console.WriteLine(GetDataInTableFormat(arrValues));
        }
    
        #endregion
    
        static void Main(string[] args)
        {           
            int value = 997;
            string[,] arrValues = new string[5, 5];
            for (int i = 0; i < arrValues.GetLength(0); i++)
            {
                for (int j = 0; j < arrValues.GetLength(1); j++)
                {
                    value++;
                    arrValues[i, j] = value.ToString();
                }
            }
            ArrayPrinter.PrintToConsole(arrValues);
            Console.ReadLine();
        }
    }
    

    【讨论】:

    • 为了获得更大的灵活性和更好的代码重用: 1. 将参数类型从StreamWriter 更改为TextWriter。 2. 将PrintToConsole 代码替换为:PrintToStream(arrValues, Console.Out); 3. ??? 4. 利润!
    【解决方案6】:

    我想要可变宽度的列,我并不特别关心方框字符。另外,我需要为每一行打印一些额外的信息。

    所以如果其他人需要,我会为你节省几分钟:

    public class TestTableBuilder
    {
    
        public interface ITextRow
        {
            String Output();
            void Output(StringBuilder sb);
            Object Tag { get; set; }
        }
    
        public class TableBuilder : IEnumerable<ITextRow>
        {
            protected class TextRow : List<String>, ITextRow
            {
                protected TableBuilder owner = null;
                public TextRow(TableBuilder Owner)
                {
                    owner = Owner;
                    if (owner == null) throw new ArgumentException("Owner");
                }
                public String Output()
                {
                    StringBuilder sb = new StringBuilder();
                    Output(sb);
                    return sb.ToString();
                }
                public void Output(StringBuilder sb)
                {
                    sb.AppendFormat(owner.FormatString, this.ToArray());
                }
                public Object Tag { get; set; }
            }
    
            public String Separator { get; set; }
    
            protected List<ITextRow> rows = new List<ITextRow>();
            protected List<int> colLength = new List<int>();
    
            public TableBuilder()
            {
                Separator = "  ";
            }
    
            public TableBuilder(String separator)
                : this()
            {
                Separator = separator;
            }
    
            public ITextRow AddRow(params object[] cols)
            {
                TextRow row = new TextRow(this);
                foreach (object o in cols)
                {
                    String str = o.ToString().Trim();
                    row.Add(str);
                    if (colLength.Count >= row.Count)
                    {
                        int curLength = colLength[row.Count - 1];
                        if (str.Length > curLength) colLength[row.Count - 1] = str.Length;
                    }
                    else
                    {
                        colLength.Add(str.Length);
                    }
                }
                rows.Add(row);
                return row;
            }
    
            protected String _fmtString = null;
            public String FormatString
            {
                get
                {
                    if (_fmtString == null)
                    {
                        String format = "";
                        int i = 0;
                        foreach (int len in colLength)
                        {
                            format += String.Format("{{{0},-{1}}}{2}", i++, len, Separator);
                        }
                        format += "\r\n";
                        _fmtString = format;
                    }
                    return _fmtString;
                }
            }
    
            public String Output()
            {
                StringBuilder sb = new StringBuilder();
                foreach (TextRow row in rows)
                {
                    row.Output(sb);
                }
                return sb.ToString();
            }
    
            #region IEnumerable Members
    
            public IEnumerator<ITextRow> GetEnumerator()
            {
                return rows.GetEnumerator();
            }
    
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return rows.GetEnumerator();
            }
    
            #endregion
        }
    
    
    
        static void Main(String[] args)
        {
            TableBuilder tb = new TableBuilder();
            tb.AddRow("When", "ID", "Name");
            tb.AddRow("----", "--", "----");
    
            tb.AddRow(DateTime.Now, "1", "Name1");
            tb.AddRow(DateTime.Now, "1", "Name2");
    
            Console.Write(tb.Output());
            Console.WriteLine();
    
            // or
    
            StringBuilder sb = new StringBuilder();
            int i = 0;
            foreach (ITextRow tr in tb)
            {
                tr.Output(sb);
                if (i++ > 1) sb.AppendLine("more stuff per line");
            }
            Console.Write(sb.ToString());
        }
    }
    

    输出:

    当 ID 名称 ---- -- ---- 2/4/2013 8:37:44 PM 1 姓名1 2/4/2013 8:37:44 PM 1 姓名2 当 ID 名称 ---- -- ---- 2/4/2013 8:37:44 PM 1 姓名1 每行更多的东西 2/4/2013 8:37:44 PM 1 姓名2 每行更多的东西

    【讨论】:

      【解决方案7】:

      我在 GitHub 上有一个项目可以使用

      https://github.com/BrunoVT1992/ConsoleTable

      你可以这样使用它:

      var table = new Table();
      
      table.SetHeaders("Name", "Date", "Number");
      
      for (int i = 0; i <= 10; i++)
      {
          if (i % 2 == 0)
              table.AddRow($"name {i}", DateTime.Now.AddDays(-i).ToLongDateString(), i.ToString());
          else
              table.AddRow($"long name {i}", DateTime.Now.AddDays(-i).ToLongDateString(), (i * 5000).ToString());
      }
      
      Console.WriteLine(table.ToString());
      

      它会给出这个结果:

      【讨论】:

      • 除了你的包之外,还有其他包,但我更喜欢你的包,因为输出更好,带有特殊字符的线路连接点。 API 很简单,不会做太多假设(例如需要标头),这很好。你会考虑在 Nuget 上添加它吗?无论如何,谢谢!
      • 不错@BrunoVT。谢谢!我也想要一个 Nuget。
      • 这是我的首选,简单,好看,可以完成工作。谢谢。
      【解决方案8】:

      如果这对某人有帮助,这是我为我的需要编写的一个简单的类。您可以轻松更改它以满足您的需求。

      using System.Collections.Generic;
      using System.Linq;
      
      namespace Utilities
      {
          public class TablePrinter
          {
              private readonly string[] titles;
              private readonly List<int> lengths;
              private readonly List<string[]> rows = new List<string[]>();
      
              public TablePrinter(params string[] titles)
              {
                  this.titles = titles;
                  lengths = titles.Select(t => t.Length).ToList();
              }
      
              public void AddRow(params object[] row)
              {
                  if (row.Length != titles.Length)
                  {
                      throw new System.Exception($"Added row length [{row.Length}] is not equal to title row length [{titles.Length}]");
                  }
                  rows.Add(row.Select(o => o.ToString()).ToArray());
                  for (int i = 0; i < titles.Length; i++)
                  {
                      if (rows.Last()[i].Length > lengths[i])
                      {
                          lengths[i] = rows.Last()[i].Length;
                      }
                  }
              }
      
              public void Print()
              {
                  lengths.ForEach(l => System.Console.Write("+-" + new string('-', l) + '-'));
                  System.Console.WriteLine("+");
      
                  string line = "";
                  for (int i = 0; i < titles.Length; i++)
                  {
                      line += "| " + titles[i].PadRight(lengths[i]) + ' ';
                  }
                  System.Console.WriteLine(line + "|");
      
                  lengths.ForEach(l => System.Console.Write("+-" + new string('-', l) + '-'));
                  System.Console.WriteLine("+");
      
                  foreach (var row in rows)
                  {
                      line = "";
                      for (int i = 0; i < row.Length; i++)
                      {
                          if (int.TryParse(row[i], out int n))
                          {
                              line += "| " + row[i].PadLeft(lengths[i]) + ' ';  // numbers are padded to the left
                          }
                          else
                          {
                              line += "| " + row[i].PadRight(lengths[i]) + ' ';
                          }
                      }
                      System.Console.WriteLine(line + "|");
                  }
      
                  lengths.ForEach(l => System.Console.Write("+-" + new string('-', l) + '-'));
                  System.Console.WriteLine("+");
              }
          }
      }
      

      示例用法:

      var t = new TablePrinter("id", "Column A", "Column B");
      t.AddRow(1, "Val A1", "Val B1");
      t.AddRow(2, "Val A2", "Val B2");
      t.AddRow(100, "Val A100", "Val B100");
      t.Print();
      

      输出:

      +-----+----------+----------+
      | id  | Column A | Column B |
      +-----+----------+----------+
      |   1 | Val A1   | Val B1   |
      |   2 | Val A2   | Val B2   |
      | 100 | Val A100 | Val B100 |
      +-----+----------+----------+
      

      【讨论】:

        【解决方案9】:

        这是对先前答案的改进。它增加了对具有不同长度的值和具有不同数量的单元格的行的支持。例如:

        ┌──────────┬─────────┬──────────────────────────┬────────────────┬─────┬───────┐
        │Identifier│     Type│               Description│  CPU Credit Use│Hours│Balance│
        ├──────────┼─────────┼──────────────────────────┼────────────────┼─────┼───────┘
        │ i-1234154│ t2.small│       This is an example.│         3263.75│  360│
        ├──────────┼─────────┼──────────────────────────┼────────────────┼─────┘
        │ i-1231412│ t2.small│  This is another example.│         3089.93│
        └──────────┴─────────┴──────────────────────────┴────────────────┘
        

        代码如下:

        public class ArrayPrinter
        {
            const string TOP_LEFT_JOINT = "┌";
            const string TOP_RIGHT_JOINT = "┐";
            const string BOTTOM_LEFT_JOINT = "└";
            const string BOTTOM_RIGHT_JOINT = "┘";
            const string TOP_JOINT = "┬";
            const string BOTTOM_JOINT = "┴";
            const string LEFT_JOINT = "├";
            const string JOINT = "┼";
            const string RIGHT_JOINT = "┤";
            const char HORIZONTAL_LINE = '─';
            const char PADDING = ' ';
            const string VERTICAL_LINE = "│";
        
            private static int[] GetMaxCellWidths(List<string[]> table)
            {
                int maximumCells = 0;
                foreach (Array row in table)
                {
                    if (row.Length > maximumCells)
                        maximumCells = row.Length;
                }
        
                int[] maximumCellWidths = new int[maximumCells];
                for (int i = 0; i < maximumCellWidths.Length; i++)
                    maximumCellWidths[i] = 0;
        
                foreach (Array row in table)
                {
                    for (int i = 0; i < row.Length; i++)
                    {
                        if (row.GetValue(i).ToString().Length > maximumCellWidths[i])
                            maximumCellWidths[i] = row.GetValue(i).ToString().Length;
                    }
                }
        
                return maximumCellWidths;
            }
        
            public static string GetDataInTableFormat(List<string[]> table)
            {
                StringBuilder formattedTable = new StringBuilder();
                Array nextRow = table.FirstOrDefault();
                Array previousRow = table.FirstOrDefault();
        
                if (table == null || nextRow == null)
                    return String.Empty;
        
                // FIRST LINE:
                int[] maximumCellWidths = GetMaxCellWidths(table);
                for (int i = 0; i < nextRow.Length; i++)
                {
                    if (i == 0 && i == nextRow.Length - 1)
                        formattedTable.Append(String.Format("{0}{1}{2}", TOP_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                    else if (i == 0)
                        formattedTable.Append(String.Format("{0}{1}", TOP_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                    else if (i == nextRow.Length - 1)
                        formattedTable.AppendLine(String.Format("{0}{1}{2}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                    else
                        formattedTable.Append(String.Format("{0}{1}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                }
        
                int rowIndex = 0;
                int lastRowIndex = table.Count - 1;
                foreach (Array thisRow in table)
                {
                    // LINE WITH VALUES:
                    int cellIndex = 0;
                    int lastCellIndex = thisRow.Length - 1;
                    foreach (object thisCell in thisRow)
                    {
                        string thisValue = thisCell.ToString().PadLeft(maximumCellWidths[cellIndex], PADDING);
        
                        if (cellIndex == 0 && cellIndex == lastCellIndex)
                            formattedTable.AppendLine(String.Format("{0}{1}{2}", VERTICAL_LINE, thisValue, VERTICAL_LINE));
                        else if (cellIndex == 0)
                            formattedTable.Append(String.Format("{0}{1}", VERTICAL_LINE, thisValue));
                        else if (cellIndex == lastCellIndex)
                            formattedTable.AppendLine(String.Format("{0}{1}{2}", VERTICAL_LINE, thisValue, VERTICAL_LINE));
                        else
                            formattedTable.Append(String.Format("{0}{1}", VERTICAL_LINE, thisValue));
        
                        cellIndex++;
                    }
        
                    previousRow = thisRow;
        
                    // SEPARATING LINE:
                    if (rowIndex != lastRowIndex)
                    {
                        nextRow = table[rowIndex + 1];
        
                        int maximumCells = Math.Max(previousRow.Length, nextRow.Length);
                        for (int i = 0; i < maximumCells; i++)
                        {
                            if (i == 0 && i == maximumCells - 1)
                            {
                                formattedTable.Append(String.Format("{0}{1}{2}", LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), RIGHT_JOINT));
                            }
                            else if (i == 0)
                            {
                                formattedTable.Append(String.Format("{0}{1}", LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                            }
                            else if (i == maximumCells - 1)
                            {
                                if (i > previousRow.Length)
                                    formattedTable.AppendLine(String.Format("{0}{1}{2}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                                else if (i > nextRow.Length)
                                    formattedTable.AppendLine(String.Format("{0}{1}{2}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                                else if (i > previousRow.Length - 1)
                                    formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                                else if (i > nextRow.Length - 1)
                                    formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                                else
                                    formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), RIGHT_JOINT));
                            }
                            else
                            {
                                if (i > previousRow.Length)
                                    formattedTable.Append(String.Format("{0}{1}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                                else if (i > nextRow.Length)
                                    formattedTable.Append(String.Format("{0}{1}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                                else
                                    formattedTable.Append(String.Format("{0}{1}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                            }
                        }
                    }
        
                    rowIndex++;
                }
        
                // LAST LINE:
                for (int i = 0; i < previousRow.Length; i++)
                {
                    if (i == 0)
                        formattedTable.Append(String.Format("{0}{1}", BOTTOM_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                    else if (i == previousRow.Length - 1)
                        formattedTable.AppendLine(String.Format("{0}{1}{2}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                    else
                        formattedTable.Append(String.Format("{0}{1}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                }
        
                return formattedTable.ToString();
            }
        }
        

        【讨论】:

        • 在经过一些测试后,您的代码无法很好地处理单列的情况。
        【解决方案10】:

        Spectre.Console 可以做到这一点。

        例子:

        简单用法:

        // Create a table
        var table = new Table();
        
        // Add some columns
        table.AddColumn("Foo");
        table.AddColumn(new TableColumn("Bar").Centered());
        
        // Add some rows
        table.AddRow("Baz", "[green]Qux[/]");
        table.AddRow(new Markup("[blue]Corgi[/]"), new Panel("Waldo"));
        
        // Render the table to the console
        AnsiConsole.Render(table);
        

        【讨论】:

          【解决方案11】:

          使用 MarkDownLog 库(可以在 NuGet 上找到)

          您可以简单地将扩展名 ToMarkdownTable() 用于任何集合,它会为您完成所有格式设置。

           Console.WriteLine(
              yourCollection.Select(s => new
                              {
                                  column1 = s.col1,
                                  column2 = s.col2,
                                  column3 = s.col3,
                                  StaticColumn = "X"
                              })
                              .ToMarkdownTable());
          

          输出看起来像这样:

          Column1  | Column2   | Column3   | StaticColumn   
          --------:| ---------:| ---------:| --------------
                   |           |           | X              
          

          【讨论】:

            【解决方案12】:
            public static void ToPrintConsole(this DataTable dataTable)
                {
                    // Print top line
                    Console.WriteLine(new string('-', 75));
            
                    // Print col headers
                    var colHeaders = dataTable.Columns.Cast<DataColumn>().Select(arg => arg.ColumnName);
                    foreach (String s in colHeaders)
                    {
                        Console.Write("| {0,-20}", s);
                    }
                    Console.WriteLine();
            
                    // Print line below col headers
                    Console.WriteLine(new string('-', 75));
            
                    // Print rows
                    foreach (DataRow row in dataTable.Rows)
                    {
                        foreach (Object o in row.ItemArray)
                        {
                            Console.Write("| {0,-20}", o.ToString());
                        }
                        Console.WriteLine();
                    }
            
                    // Print bottom line
                    Console.WriteLine(new string('-', 75));
                }
            

            【讨论】:

              【解决方案13】:

              @grampa 答案的扩展。现在表格居中对齐。从两端添加了额外的填充。

              public class TablePrinter
              {
                  const string TOP_LEFT_JOINT = "┌";
                  const string TOP_RIGHT_JOINT = "┐";
                  const string BOTTOM_LEFT_JOINT = "└";
                  const string BOTTOM_RIGHT_JOINT = "┘";
                  const string TOP_JOINT = "┬";
                  const string BOTTOM_JOINT = "┴";
                  const string LEFT_JOINT = "├";
                  const string JOINT = "┼";
                  const string RIGHT_JOINT = "┤";
                  const char HORIZONTAL_LINE = '─';
                  const char PADDING = ' ';
                  const string VERTICAL_LINE = "│";
              
                  private static int[] GetMaxCellWidths(List<string[]> table)
                  {
                      int maximumCells = 0;
                      foreach (Array row in table)
                      {
                          if (row.Length > maximumCells)
                              maximumCells = row.Length;
                      }
              
                      int[] maximumCellWidths = new int[maximumCells];
                      for (int i = 0; i < maximumCellWidths.Length; i++)
                          maximumCellWidths[i] = 0;
              
                      foreach (Array row in table)
                      {
                          for (int i = 0; i < row.Length; i++)
                          {
                              if (row.GetValue(i).ToString().Length > maximumCellWidths[i])
                                  maximumCellWidths[i] = row.GetValue(i).ToString().Length +2;
                          }
                      }
              
                      return maximumCellWidths;
                  }
              
                  public static string GetDataInTableFormat(List<string[]> table)
                  {
                      StringBuilder formattedTable = new StringBuilder();
                      Array nextRow = table.FirstOrDefault();
                      Array previousRow = table.FirstOrDefault();
              
                      if (table == null || nextRow == null)
                          return String.Empty;
              
                      // FIRST LINE:
                      int[] maximumCellWidths = GetMaxCellWidths(table);
                      for (int i = 0; i < nextRow.Length; i++)
                      {
                          if (i == 0 && i == nextRow.Length - 1)
                              formattedTable.Append(String.Format("{0}{1}{2}", TOP_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                          else if (i == 0)
                              formattedTable.Append(String.Format("{0}{1}", TOP_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE)));
                          else if (i == nextRow.Length - 1)
                              formattedTable.AppendLine(String.Format("{0}{1}{2}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                          else
                              formattedTable.Append(String.Format("{0}{1}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE)));
                      }
              
                      int rowIndex = 0;
                      int lastRowIndex = table.Count - 1;
                      foreach (Array thisRow in table)
                      {
                          // LINE WITH VALUES:
                          int cellIndex = 0;
                          int lastCellIndex = thisRow.Length - 1;
                          foreach (object thisCell in thisRow)
                          {
                              string thisValue = String.Format("{0}{1}{2}", String.Empty.PadLeft((maximumCellWidths[cellIndex] - thisCell.ToString().Length)/ 2),thisCell.ToString(),String.Empty.PadRight(((maximumCellWidths[cellIndex] - thisCell.ToString().Length + 1) / 2)));
              
                              if (cellIndex == 0 && cellIndex == lastCellIndex)
                                  formattedTable.AppendLine(String.Format("{0}{1}{2}", VERTICAL_LINE, thisValue, VERTICAL_LINE));
                              else if (cellIndex == 0)
                                  formattedTable.Append(String.Format("{0}{1}", VERTICAL_LINE, thisValue));
                              else if (cellIndex == lastCellIndex)
                                  formattedTable.AppendLine(String.Format("{0}{1}{2}", VERTICAL_LINE, thisValue, VERTICAL_LINE));
                              else
                                  formattedTable.Append(String.Format("{0}{1}", VERTICAL_LINE, thisValue));
              
                              cellIndex++;
                          }
              
                          previousRow = thisRow;
              
                          // SEPARATING LINE:
                          if (rowIndex != lastRowIndex)
                          {
                              nextRow = table[rowIndex + 1];
              
                              int maximumCells = Math.Max(previousRow.Length, nextRow.Length);
                              for (int i = 0; i < maximumCells; i++)
                              {
                                  if (i == 0 && i == maximumCells - 1)
                                  {
                                      formattedTable.Append(String.Format("{0}{1}{2}", LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), RIGHT_JOINT));
                                  }
                                  else if (i == 0)
                                  {
                                      formattedTable.Append(String.Format("{0}{1}", LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                                  }
                                  else if (i == maximumCells - 1)
                                  {
                                      if (i > previousRow.Length)
                                          formattedTable.AppendLine(String.Format("{0}{1}{2}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                                      else if (i > nextRow.Length)
                                          formattedTable.AppendLine(String.Format("{0}{1}{2}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                                      else if (i > previousRow.Length - 1)
                                          formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE), TOP_RIGHT_JOINT));
                                      else if (i > nextRow.Length - 1)
                                          formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight((maximumCellWidths[i]), HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                                      else
                                          formattedTable.AppendLine(String.Format("{0}{1}{2}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight((maximumCellWidths[i]), HORIZONTAL_LINE), RIGHT_JOINT));
                                  }
                                  else
                                  {
                                      if (i > previousRow.Length)
                                          formattedTable.Append(String.Format("{0}{1}", TOP_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE)));
                                      else if (i > nextRow.Length)
                                          formattedTable.Append(String.Format("{0}{1}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE)));
                                      else
                                          formattedTable.Append(String.Format("{0}{1}", JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE).PadRight(maximumCellWidths[i], HORIZONTAL_LINE)));
                                  }
                              }
                          }
              
                          rowIndex++;
                      }
              
                      // LAST LINE:
                      for (int i = 0; i < previousRow.Length; i++)
                      {
                          if (i == 0)
                              formattedTable.Append(String.Format("{0}{1}", BOTTOM_LEFT_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                          else if (i == previousRow.Length - 1)
                              formattedTable.AppendLine(String.Format("{0}{1}{2}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE), BOTTOM_RIGHT_JOINT));
                          else
                              formattedTable.Append(String.Format("{0}{1}", BOTTOM_JOINT, String.Empty.PadLeft(maximumCellWidths[i], HORIZONTAL_LINE)));
                      }
              
                      return formattedTable.ToString();
                  }
              

              }

              输出格式很好,看起来很漂亮 ┌──────────┬────────────┬────────────┬────────────┬── ────────┬────────┐ │ Root Id │ HotelRate │ percentan │ RightValue │ Occupied │ Closed │ ├──────────┼────────────┼────────────┼────────────┼── ────────┼────────┤ │ 3034 │ 100 │ 40.816306 │ 15 │ -0 │ -0 │ ├──────────┼────────────┼────────────┼────────────┼── ────────┼────────┤ │ 3035 │ 100 │ 16.666666 │ 0 │ -0 │ 45 │ ├──────────┼────────────┼────────────┼────────────┼── ────────┼────────┤ │ 3036 │ 100 │ 44.44444 │ 0 │ -0 │ 30 │ └──────────┴────────────┴────────────┴────────────┴── ────────┴────────┘ └──────────┴────────────┴────────────┴────────────┴── ────────┴────────┘ 不知怎的,我无法在这里格式化。

              【讨论】:

                【解决方案14】:

                在 VisualBasic.net 中更简单!

                如果您希望用户能够手动将数据输入到表格中:

                Console.Write("Enter Data For Column 1: ")
                    Dim Data1 As String = Console.ReadLine
                    Console.Write("Enter Data For Column 2: ")
                    Dim Data2 As String = Console.ReadLine
                
                    Console.WriteLine("{0,-20} {1,-10} {2,-10}", "{Data Type}", "{Column 1}", "{Column 2}")
                    Console.WriteLine("{0,-20} {1,-10} {2,-10}", "Data Entered:", Data1, Data2)
                
                    Console.WriteLine("ENTER To Exit: ")
                    Console.ReadLine()
                

                应该是这样的:

                【讨论】:

                • 我认为这很有用,因为我想要的只是 C# 中的简单列对齐,并且 Console.WriteLine() 在两种语言中的工作方式相似。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-10-09
                • 2010-12-13
                • 2010-10-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多