如何: 在控制台应用程序(C #)中绘制表的最佳方式

我有个有趣的问题。 假设我有很多数据在非常快的时间间隔内变化。 我想在控制台 app. f.ex 中将这些数据显示为一个表格:

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

如何保持快速和如何确定列宽度?我知道如何在 java 中做到这一点,但我不知道如何在 C # 中完成。

198365 次浏览

使用带对齐值的 字符串,格式

例如:

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

创建一个格式化行。

你可以这样做:

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

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

如果有人需要的话,我可以帮你节省几分钟:

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

产出:

When                 ID  Name
----                 --  ----
2/4/2013 8:37:44 PM  1   Name1
2/4/2013 8:37:44 PM  1   Name2


When                 ID  Name
----                 --  ----
2/4/2013 8:37:44 PM  1   Name1
more stuff per line
2/4/2013 8:37:44 PM  1   Name2
more stuff per line

编辑: 感谢@superlogic,您现在可以在 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 TableParser.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;
}
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));
}

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

┌──────────┬─────────┬──────────────────────────┬────────────────┬─────┬───────┐
│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();
}
}

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

它应该是这样的:

It should look like this (Click Me).

使用 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

有几个开源库允许控制台表格格式化,从简单的(如这里答案中的代码示例)到更高级的。

康索台

从 NuGet 统计数据来看,最流行的表格格式库是 康索台。表的构造如下(来自自述文件) :

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    |
--------------------------------------------------

此库期望单行单元格没有格式设置。

有两个基于 Console Table 的库,它们的特性集稍微扩展了一些,比如更多的行样式。

控制台格式

如果需要更复杂的格式,可以使用 控制台格式。下表来自一个过程列表(来自一个示例项目) :

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")),
})
)

最终的结果会是这样的:

它支持任何类型的表格线(包括一些和可定制的) ,多行单元格与字换行,颜色,列增长的基础上的内容或百分比,文本对齐等。

? ? 控制台格式由我开发。

如果这对某人有帮助的话,这是一个简单的类,我根据自己的需要编写的。你可以很容易地改变它以适应你的需要。

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 |
+-----+----------+----------+

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

它将产生这样的结果:

enter image description here

控制台可以做到这一点。

例如:

Tables are a perfect way of displaying tabular data in a terminal.

简单用法:

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

扩展到@grampa answer。现在表格居中对齐。从两端增加了额外的填充。

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   │
└─────────┴───────────┴───────────┴────────────┴──────────┴────────┘

我将通过添加 $来使用字符串插值,如下例所示:

$"|{arg0,5}|{arg1,5}|{arg2,5}|{arg3,5}|";

举个简单的例子:

----------------------------------
| Column 1 | Column 2 | Column 3 |
----------------------------------
|Left11    |        12|        13|
|Left21    |        22|        23|
|Left31    |        32|        33|
|Left41    |        42|        43|

密码如下:

var list = new List<Data>()
{
new() {S1 = "Left11", S2 = "12", S3 = "13"},
new() {S1 = "Left21", S2 = "22", S3 = "23"},
new() {S1 = "Left31", S2 = "32", S3 = "33"},
new() {S1 = "Left41", S2 = "42", S3 = "43"}
};


var lineSep = "----------------------------------";
var header = $"|{" Column 1 ",10}|{" Column 2 ",10}|{" Column 3 ",10}|";
Console.WriteLine(lineSep);
Console.WriteLine(header);
Console.WriteLine(lineSep);


foreach (var data in list)
{
var line = $"|{data.S1,-10}|{data.S2,10}|{data.S3,10}|";
Console.WriteLine(line);
}




class Data
{
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
}

我提供我的选择:

Https://github.com/grizzly-pride/console_menu_tools.git

MenuBuilder menuTable = new(1, 60, 3, 1, true)
{    ItemsMenu = new()
{
myTable.AddTopLine(),
myTable.AddHeader(),
myTable.AddMiddleLine(),
myTable.AddRow("The Witcher 3", "action/RPG", "05.02.2013"),
myTable.AddRow("Diablo 2", "RPG", "05.07.2002"),
myTable.AddRow("Warcraft 3", "TBS", "29.06.2000"),
myTable.AddRow("Quake 2", "Shooter", "18.02.1997"),
myTable.AddRow("Grand Theft Auto: Vice City", "action-adventure",
"27.10.2002"),
myTable.AddEndLine(),
},
PointerColor = ConsoleColor.Yellow,
ItemColor = ConsoleColor.Green
};


menuTable.SetCursorVisible(false);
int selectRow = menuTable.RunMenu();


Console.WriteLine($"\n\n -> Selected: {selectRow}");

在此输入图像描述