如何:在控制台应用程序中绘制表格的最佳方式(C#)
我有一个有趣的问题。 想象一下,我有很多数据以非常快的速度变化。 我想在控制台应用程序中将该数据显示为表格。 f.ex:
------------------------------------------------------------------------- | Column 1 | Column 2 | Column 3 | Column 4 | ------------------------------------------------------------------------- | | | | | | | | | | | | | | | -------------------------------------------------------------------------
如何保持快速以及如何修正列宽? 我知道如何做到这一点,但我不知道如何在C#中完成。
你可以做如下的事情:
static int tableWidth = 77; 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); } }
使用带有alignment值的String.Format 。
例如:
String.Format("|{0,5}|{1,5}|{2,5}|{3,5}|", arg0, arg1, arg2, arg3);
创build一个格式化的行。
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(); } }
编辑:感谢@superlogical,你现在可以在github中find并改进下面的代码!
我根据这里的一些想法写了这个课。 列的宽度是最优的,它可以用这个简单的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 | */ }
这是class级:
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
(请注意,由于reflection,它会慢一点):
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; }
我想要可变宽度的列,我并不特别在意盒子的字符。 另外,我需要为每行打印一些额外的信息。
所以如果有其他人需要,我会省几分钟:
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 Name1 2/4/2013 8:37:44 PM 1 Name2 当ID名称 ---- - ---- 2/4/2013 8:37:44 PM 1 Name1 每行更多的东西 2/4/2013 8:37:44 PM 1 Name2 每行更多的东西
这是对以前答案的改进。 它增加了对不同长度和不同数量的单元格的值的支持。 例如:
┌──────────┬─────────┬──────────────────────────┬────────────────┬─────┬───────┐ │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(); } }
使用MarkDownLog库(你可以在NuGet上find它)
你可以简单地使用扩展ToMarkdownTable()到任何集合,它为你做所有的格式。
Console.WriteLine( yourCollection.Select(s => new { column1 = s.col1, column2 = s.col2, column3 = s.col3, StaticColumn = "X" }) .ToMarkdownTable());
输出看起来像这样:
Column1 | Column2 | Column3 | StaticColumn --------:| ---------:| ---------:| -------------- | | | X
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)); }
在VisualBasic.net中更容易!
如果您希望用户能够手动将数据input到表中,请执行以下操作:
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()
它应该是这样的: