using System; using System.Collections.Generic; using System.Data; using System.Linq; namespace Terminal.Gui.Views { /// /// View for tabular data based on a /// public class TableView : View { private int columnOffset; private int rowOffset; public DataTable Table { get; private set; } /// /// Zero indexed offset for the upper left to display in . /// /// This property allows very wide tables to be rendered with horizontal scrolling public int ColumnOffset { get { return columnOffset; } //try to prevent this being set to an out of bounds column set { //the value before we changed it var origValue = columnOffset; columnOffset = Math.Min (Table.Columns.Count - 1, Math.Max (0, value)); //if value actually changed we must update UI if(columnOffset != origValue) SetNeedsDisplay(); } } /// /// Zero indexed offset for the to display in on line 2 of the control (first line being headers) /// /// This property allows very wide tables to be rendered with horizontal scrolling public int RowOffset { get { return rowOffset; } set { //the value before we changed it var origValue = rowOffset; rowOffset = Math.Min (Table.Rows.Count - 1, Math.Max (0, value)); //if value actually changed we must update UI if(rowOffset != origValue) SetNeedsDisplay(); } } /// /// The maximum number of characters to render in any given column. This prevents one long column from pushing out all the others /// public int MaximumCellWidth {get;set;} = 100; /// /// The text representation that should be rendered for cells with the value /// public string NullSymbol {get;set;} = "-"; /// /// The symbol to add after each cell value and header value to visually seperate values /// public char SeparatorSymbol {get;set; } = ' '; /// /// Initialzies a class using layout. /// /// The table to display in the control public TableView (DataTable table) : base () { this.Table = table ?? throw new ArgumentNullException (nameof (table)); } /// public override void Redraw (Rect bounds) { Attribute currentAttribute; var current = ColorScheme.Focus; Driver.SetAttribute (current); Move (0, 0); var frame = Frame; int activeColor = ColorScheme.HotNormal; int trackingColor = ColorScheme.HotFocus; // What columns to render at what X offset in viewport Dictionary columnsToRender = CalculateViewport(bounds); Driver.SetAttribute (ColorScheme.HotNormal); //invalidate current row (prevents scrolling around leaving old characters in the frame Driver.AddStr(new string (' ',bounds.Width)); // Render the headers foreach(var kvp in columnsToRender) { Move (kvp.Value,0); Driver.AddStr(kvp.Key.ColumnName+ SeparatorSymbol); } //render the cells for (int line = 1; line < frame.Height; line++) { //invalidate current row (prevents scrolling around leaving old characters in the frame Move (0,line); Driver.AddStr(new string (' ',bounds.Width)); //work out what Row to render var rowToRender = RowOffset + (line-1); //if we have run off the end of the table if(rowToRender >= Table.Rows.Count) continue; foreach(var kvp in columnsToRender) { Move (kvp.Value,line); Driver.AddStr(GetRenderedVal(Table.Rows[rowToRender][kvp.Key]) + SeparatorSymbol); } } void SetAttribute (Attribute attribute) { if (currentAttribute != attribute) { currentAttribute = attribute; Driver.SetAttribute (attribute); } } } /// public override bool ProcessKey (KeyEvent keyEvent) { switch (keyEvent.Key) { case Key.CursorLeft: ColumnOffset--; break; case Key.CursorRight: ColumnOffset++; break; case Key.CursorDown: RowOffset++; break; case Key.CursorUp: RowOffset--; break; case Key.PageUp: RowOffset -= Frame.Height; break; case Key.V | Key.CtrlMask: case Key.PageDown: RowOffset += Frame.Height; break; case Key.Home | Key.CtrlMask: RowOffset = 0; ColumnOffset = 0; break; case Key.Home: ColumnOffset = 0; break; case Key.End | Key.CtrlMask: //jump to end of table RowOffset = Table.Rows.Count-1; ColumnOffset = Table.Columns.Count-1; break; case Key.End: //jump to end of row ColumnOffset = Table.Columns.Count-1; break; } PositionCursor (); return true; } /// /// Calculates which columns should be rendered given the in which to display and the /// /// /// /// private Dictionary CalculateViewport(Rect bounds, int padding = 1) { Dictionary toReturn = new Dictionary(); int usedSpace = 0; int availableHorizontalSpace = bounds.Width; int rowsToRender = bounds.Height-1; //1 reserved for the headers row foreach(var col in Table.Columns.Cast().Skip(ColumnOffset)) { toReturn.Add(col,usedSpace); usedSpace += CalculateMaxRowSize(col,rowsToRender) + padding; if(usedSpace > availableHorizontalSpace) return toReturn; } return toReturn; } /// /// Returns the maximum of the name and the maximum length of data that will be rendered starting at and rendering /// /// /// /// private int CalculateMaxRowSize (DataColumn col, int rowsToRender) { int spaceRequired = col.ColumnName.Length; for(int i = RowOffset; i /// Returns the value that should be rendered to best represent a strongly typed read from /// /// /// private string GetRenderedVal (object value) { if(value == null || value == DBNull.Value) { return NullSymbol; } var representation = value.ToString(); //if it is too long to fit if(representation.Length > MaximumCellWidth) return representation.Substring(0,MaximumCellWidth); return representation; } } }