Skip to content

Commit 7cd54b6

Browse files
committed
Making the data grid style customizable.
1 parent 7e2e24d commit 7cd54b6

File tree

6 files changed

+208
-50
lines changed

6 files changed

+208
-50
lines changed

ConsoleGUI.Example/Program.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ static void Main()
2222
{
2323
Columns = new[]
2424
{
25-
new DataGrid<Player>.ColumnDefinition("Name", 10, p => p.Name, foreground: p => p.Name == "Tomasz" ? (Color?)new Color(100, 100, 220) : null),
25+
new DataGrid<Player>.ColumnDefinition("Name", 10, p => p.Name, foreground: p => p.Name == "Tomasz" ? (Color?)new Color(100, 100, 220) : null, textAlignment: TextAlignment.Right),
2626
new DataGrid<Player>.ColumnDefinition("Surname", 10, p => p.Surname),
27-
new DataGrid<Player>.ColumnDefinition("Birth date", 15, p => p.BirthDate.ToShortDateString()),
28-
new DataGrid<Player>.ColumnDefinition("Points", 5, p => p.Points.ToString(), background: p => p.Points > 20 ? (Color?)new Color(0, 220, 0) : null)
27+
new DataGrid<Player>.ColumnDefinition("Birth date", 15, p => p.BirthDate.ToShortDateString(), textAlignment: TextAlignment.Center),
28+
new DataGrid<Player>.ColumnDefinition("Points", 5, p => p.Points.ToString(), background: p => p.Points > 20 ? (Color?)new Color(0, 220, 0) : null, textAlignment: TextAlignment.Right)
2929
},
3030
Data = new[]
3131
{
3232
new Player("John", "Connor", new DateTime(1985, 2, 28), 10),
3333
new Player("Ellen", "Ripley", new DateTime(2092, 1, 1), 23),
3434
new Player("Jan", "Kowalski", new DateTime(1990, 4, 10), 50),
3535
new Player("Tomasz", "Rewak", new DateTime(1900, 1, 1), 0),
36-
}
36+
},
37+
Style = DataGridStyle.AllBorders
3738
};
3839

3940
var tabPanel = new TabPanel();

ConsoleGUI/Controls/Border.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
using ConsoleGUI.Data;
33
using ConsoleGUI.Space;
44
using ConsoleGUI.Utils;
5-
using System;
6-
using System.Collections.Generic;
7-
using System.Text;
85

96
namespace ConsoleGUI.Controls
107
{

ConsoleGUI/Controls/DataGrid.cs

Lines changed: 149 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,63 +5,125 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8-
using System.Text;
98

109
namespace ConsoleGUI.Controls
1110
{
1211
public class DataGrid<T> : Control
1312
{
1413
public class ColumnDefinition
1514
{
16-
public readonly string Header;
15+
private readonly Func<int, Character> _headerSelector;
16+
private readonly Func<T, int, Character> _valueSelector;
17+
1718
public readonly int Width;
18-
public readonly Func<T, int, Character> Selector;
1919

20-
public ColumnDefinition(string header, int width, Func<T, int, Character> selector)
20+
public ColumnDefinition(string header, int width, Func<T, string> selector)
2121
{
22-
Header = header;
2322
Width = width;
24-
Selector = selector;
23+
24+
_headerSelector = i =>
25+
{
26+
var text = header;
27+
return i < text.Length ? new Character(text[i]) : Character.Empty;
28+
};
29+
30+
_valueSelector = (v, i) =>
31+
{
32+
var text = selector(v);
33+
return i < text.Length ? new Character(text[i]) : Character.Empty;
34+
};
2535
}
2636

27-
public ColumnDefinition(string header, int width, Func<T, string> selector)
37+
public ColumnDefinition(string header, int width, Func<T, int, Character> selector)
2838
{
29-
Header = header;
3039
Width = width;
31-
Selector = (v, i) =>
40+
41+
_headerSelector = i =>
3242
{
33-
var text = selector(v);
43+
var text = header;
3444
return i < text.Length ? new Character(text[i]) : Character.Empty;
3545
};
46+
47+
_valueSelector = selector;
48+
}
49+
50+
public ColumnDefinition(int width, Func<int, Character> headerSelector, Func<T, int, Character> valueSelector)
51+
{
52+
Width = width;
53+
54+
_headerSelector = headerSelector;
55+
_valueSelector = valueSelector;
3656
}
3757

38-
public ColumnDefinition(string header, int width, Func<T, string> selector, Func<T, Color?> foreground = null, Func<T, Color?> background = null)
58+
public ColumnDefinition(
59+
string header,
60+
int width,
61+
Func<T, string> selector,
62+
Func<T, Color?> foreground = null,
63+
Func<T, Color?> background = null,
64+
TextAlignment textAlignment = TextAlignment.Left)
3965
{
40-
Header = header;
4166
Width = width;
42-
Selector = (v, i) =>
67+
68+
_headerSelector = i =>
69+
{
70+
var text = header;
71+
return i < text.Length ? new Character(text[i]) : Character.Empty;
72+
};
73+
74+
_valueSelector = (v, i) =>
4375
{
4476
var text = selector(v);
45-
return new Character(i < text.Length ? (char?)text[i] : null, foreground?.Invoke(v), background?.Invoke(v));
77+
78+
if (textAlignment == TextAlignment.Center)
79+
i -= (Width - text.Length) / 2;
80+
if (textAlignment == TextAlignment.Right)
81+
i -= Width - text.Length;
82+
83+
return new Character(
84+
i >= 0 && i < text.Length ? (char?)text[i] : null,
85+
foreground?.Invoke(v),
86+
background?.Invoke(v));
4687
};
4788
}
89+
90+
internal Character GetHeader(int xOffset) => _headerSelector(xOffset);
91+
internal Character GetValue(T value, int xOffset) => _valueSelector(value, xOffset);
4892
}
4993

50-
private ColumnDefinition[] columns = new ColumnDefinition[0];
94+
private ColumnDefinition[] _columns = new ColumnDefinition[0];
5195
public ColumnDefinition[] Columns
5296
{
53-
get => columns;
97+
get => _columns;
5498
set => Setter
55-
.Set(ref columns, value.ToArray())
99+
.Set(ref _columns, value.ToArray())
56100
.Then(Initialize);
57101
}
58102

59-
private IReadOnlyCollection<T> data = new T[0];
103+
private IReadOnlyCollection<T> _data = new T[0];
60104
public IReadOnlyCollection<T> Data
61105
{
62-
get => data;
106+
get => _data;
63107
set => Setter
64-
.Set(ref data, value)
108+
.Set(ref _data, value)
109+
.Then(Initialize);
110+
}
111+
112+
private DataGridStyle _style = DataGridStyle.AllBorders;
113+
public DataGridStyle Style
114+
{
115+
get => _style;
116+
set => Setter
117+
.Set(ref _style, value)
118+
.Then(Initialize);
119+
}
120+
121+
private bool _showHeader = true;
122+
public bool ShowHeader
123+
{
124+
get => _showHeader;
125+
set => Setter
126+
.Set(ref _showHeader, value)
65127
.Then(Initialize);
66128
}
67129

@@ -89,33 +151,80 @@ public override Cell this[Position position]
89151
{
90152
get
91153
{
154+
if (position.Y < 0) return Character.Empty;
155+
if (position.X < 0) return Character.Empty;
156+
if (position.Y >= CalculateDesiredHeight()) return Character.Empty;
157+
if (position.X >= CalculateDesiredWidth()) return Character.Empty;
158+
92159
int column = 0;
93-
int xOffset = 0;
94-
if (position.Y > Data.Count * 2) return Character.Empty;
95-
while (column < Columns.Length && position.X > xOffset + Columns[column].Width) xOffset += Columns[column++].Width + 1;
96-
97-
int x = position.X - xOffset;
98-
if (column >= Columns.Length) return Character.Empty;
99-
if (column == Columns.Length - 1 && x == Columns[column].Width) return Character.Empty;
100-
if (position.Y == 1 && x == Columns[column].Width) return new Character('╪');
101-
if (position.Y == 1) return new Character('═');
102-
if (position.Y % 2 == 1 && x == Columns[column].Width) return new Character('┼');
103-
if (position.Y % 2 == 1) return new Character('─');
104-
if (x == Columns[column].Width) return new Character('│');
105-
if (position.Y == 0) return x < Columns[column].Header.Length ? new Character(Columns[column].Header[x]) : Character.Empty;
106-
return Columns[column].Selector(Data.ElementAt(position.Y / 2 - 1), x);
160+
int xOffset = position.X;
161+
bool isVerticalBorder = false;
162+
163+
while (xOffset >= Columns[column].Width)
164+
{
165+
if (Style.HasVertivalBorders && xOffset == Columns[column].Width)
166+
{
167+
isVerticalBorder = true;
168+
break;
169+
}
170+
171+
xOffset -= Columns[column].Width;
172+
xOffset -= Style.HasVertivalBorders ? 1 : 0;
173+
column += 1;
174+
}
175+
176+
if (ShowHeader && position.Y == 0 && isVerticalBorder) return Style.HeaderVerticalBorder.Value;
177+
if (ShowHeader && position.Y == 1 && isVerticalBorder && Style.HeaderIntersectionBorder.HasValue) return Style.HeaderIntersectionBorder.Value;
178+
if (ShowHeader && position.Y == 1 && Style.HeaderHorizontalBorder.HasValue) return Style.HeaderHorizontalBorder.Value;
179+
if (ShowHeader && position.Y == 0) return Columns[column].GetHeader(xOffset);
180+
181+
int yOffset = position.Y;
182+
183+
if (ShowHeader) yOffset--;
184+
if (ShowHeader && Style.HeaderHorizontalBorder.HasValue) yOffset--;
185+
186+
int row = Style.CellHorizontalBorder.HasValue
187+
? yOffset / 2
188+
: yOffset;
189+
bool isHorizontalBorder = Style.CellHorizontalBorder.HasValue
190+
? yOffset % 2 == 1
191+
: false;
192+
193+
if (isHorizontalBorder && isVerticalBorder) return Style.CellIntersectionBorder.Value;
194+
if (isHorizontalBorder) return Style.CellHorizontalBorder.Value;
195+
if (isVerticalBorder) return Style.CellVerticalBorder.Value;
196+
197+
return Columns[column].GetValue(Data.ElementAt(row), xOffset);
107198
}
108199
}
109200

110201
protected override void Initialize()
111202
{
112-
using (Freeze())
113-
{
114-
int width = Columns.Sum(c => c.Width) + Math.Max(0, Columns.Length - 1);
115-
int height = Data.Count * 2 + 1;
203+
Resize(new Size(CalculateDesiredWidth(), CalculateDesiredHeight()));
204+
}
116205

117-
Resize(new Size(width, height));
118-
}
206+
private int CalculateDesiredHeight()
207+
{
208+
int height = Data.Count;
209+
210+
if (ShowHeader)
211+
height += 1;
212+
if (ShowHeader && Style.HeaderHorizontalBorder.HasValue)
213+
height += 1;
214+
if (Data.Count > 0 && Style.CellHorizontalBorder.HasValue)
215+
height += Data.Count - 1;
216+
217+
return height;
218+
}
219+
220+
private int CalculateDesiredWidth()
221+
{
222+
int width = Columns.Sum(c => c.Width);
223+
224+
if (Columns.Length > 0 && Style.HasVertivalBorders)
225+
width += Columns.Length - 1;
226+
227+
return width;
119228
}
120229
}
121230
}

ConsoleGUI/Controls/Grid.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ public RowDefinition(int height)
3131
}
3232
}
3333

34-
private DrawingContext[,] children = new DrawingContext[0, 0];
34+
private DrawingContext[,] _children = new DrawingContext[0, 0];
3535
private DrawingContext[,] Children
3636
{
37-
get => children;
37+
get => _children;
3838
set => Setter
39-
.Set(ref children, value);
39+
.Set(ref _children, value);
4040
}
4141

4242
private ColumnDefinition[] _columns = new ColumnDefinition[0];

ConsoleGUI/Data/DataGridStyle.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
3+
namespace ConsoleGUI.Data
4+
{
5+
public readonly struct DataGridStyle
6+
{
7+
public readonly Character? HeaderHorizontalBorder;
8+
public readonly Character? HeaderVerticalBorder;
9+
public readonly Character? HeaderIntersectionBorder;
10+
public readonly Character? CellHorizontalBorder;
11+
public readonly Character? CellVerticalBorder;
12+
public readonly Character? CellIntersectionBorder;
13+
14+
public DataGridStyle(Character? headerHorizontalBorder, Character? headerVerticalBorder, Character? headerIntersectionBorder, Character? cellHorizontalBorder, Character? cellVerticalBorder, Character? cellIntersectionBorder)
15+
{
16+
if (headerVerticalBorder.HasValue ^ cellVerticalBorder.HasValue)
17+
throw new InvalidOperationException($"The {nameof(HeaderVerticalBorder)} and the {nameof(CellVerticalBorder)} have to either both be set or both be left empty.");
18+
if ((headerHorizontalBorder.HasValue && headerVerticalBorder.HasValue) ^ headerIntersectionBorder.HasValue)
19+
throw new InvalidOperationException($"The {nameof(HeaderIntersectionBorder)} needs to be set when and only when the {nameof(HeaderHorizontalBorder)} and the {nameof(HeaderVerticalBorder)} are both set");
20+
if ((cellHorizontalBorder.HasValue && cellVerticalBorder.HasValue) ^ cellIntersectionBorder.HasValue)
21+
throw new InvalidOperationException($"The {nameof(CellIntersectionBorder)} needs to be set when and only when the {nameof(CellHorizontalBorder)} and the {nameof(CellVerticalBorder)} are both set");
22+
23+
HeaderHorizontalBorder = headerHorizontalBorder;
24+
HeaderVerticalBorder = headerVerticalBorder;
25+
HeaderIntersectionBorder = headerIntersectionBorder;
26+
CellHorizontalBorder = cellHorizontalBorder;
27+
CellVerticalBorder = cellVerticalBorder;
28+
CellIntersectionBorder = cellIntersectionBorder;
29+
}
30+
31+
internal bool HasVertivalBorders => HeaderVerticalBorder.HasValue;
32+
33+
public static DataGridStyle AllBorders => new DataGridStyle(new Character('═'), new Character('│'), new Character('╪'), new Character('─'), new Character('│'), new Character('┼'));
34+
public static DataGridStyle NoHorizontalCellBorders => new DataGridStyle(new Character('═'), new Character('│'), new Character('╪'), null, new Character('│'), null);
35+
public static DataGridStyle OnlyHorizontalHeaderBorder => new DataGridStyle(new Character('═'), null, null, null, null, null);
36+
public static DataGridStyle NoBorders => new DataGridStyle(null, null, null, null, null, null);
37+
}
38+
}

ConsoleGUI/Data/TextAlignment.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace ConsoleGUI.Data
6+
{
7+
public enum TextAlignment
8+
{
9+
Left,
10+
Center,
11+
Right
12+
}
13+
}

0 commit comments

Comments
 (0)