GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPALControls.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Concurrent;
19using System.Collections.Generic;
20using System.Drawing;
21using System.Linq;
22using System.Text;
23using System.Text.RegularExpressions;
24using System.Threading.Tasks;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
27using static GenerallyPositive.Enums;
28
30{
35 {
36 }
37 #region <GPAL Controls>
42 {
47 public Font FontType { get; internal set; } = new Font("Georgia", 12);
52 public ContentAlignment TextAlignment { get; internal set; } = ContentAlignment.MiddleLeft;
57 public bool AutoSize { get; internal set; } = true;
62 public ContentAlignment ControlAlignment { get; internal set; } = ContentAlignment.MiddleLeft;
69 {
70 FontType = font;
71 return this;
72 }
73
78 public IAllowControlSettingsAndFontSettings WithTextAlignment(ContentAlignment textAlignment)
79 {
80 TextAlignment = textAlignment;
81 return this;
82 }
83
90 public IAllowControlSettings WithAlignment(ContentAlignment alignment)
91 {
92 throw new NotImplementedException();
93 }
94
100 {
101 AutoSize = autoSize;
102 return this;
103 }
104
109 public int Height { get; internal set; } = 0;
110 private string _text;
116 public string Text
117 {
118 get => _text;
119 set
120 {
121 _text = value;
122 if (WindowsControl is Control ctrl && true == Alive(ctrl))
123 {
124 OnUi(ctrl, () => ctrl.Text = value);
125 }
126 }
127 }
128
132 public string Name { get; internal set; }
136 public object Tag { get; internal set; }
141 public dynamic WindowsControl { get; internal set; }
142 public bool IsReady => WindowsControl != null;
143
154 internal static bool Alive(Control control)
155 {
156 // whether the control is still there, and nothing about whether it can be marshalled to. a control
157 // on a tab nobody has opened has no handle yet and is very much still there: its value is set on it
158 // now and painted when the tab is first shown. deciding that here would throw those writes away,
159 // so the handle is asked about where the marshalling happens instead
160 return null != control
161 && false == control.IsDisposed
162 && false == control.Disposing;
163 }
164
165 public void Focus()
166 {
167 if (WindowsControl is Control ctrl && true == Alive(ctrl))
168 ctrl.Focus();
169 }
173 public FormControlType ControlType { get; internal set; }
174 internal bool ReadOnly { get; set; }
175 internal bool IsChecked { get; set; }
176 internal bool IsEnabled { get; set; } = true;
177 internal string ToolTipText { get; private set; }
178 internal int ToolTipAutoPopDelayMs { get; private set; } = 5000; // default 5 seconds
179 internal System.Windows.Forms.Keys ShortCutKey { get; set; }
180 internal List<(ControlEventType EventType, Delegate Handler)> Callbacks { get; } = new List<(ControlEventType EventType, Delegate Handler)>();
181
199 where TDelegate : Delegate
200 {
201 if (handler is EventHandler typed)
202 Callbacks.Add((ControlEventType.Default, (EventHandler)((sender, args) => RunOnUiThread(typed, sender, args))));
203 else
204 Callbacks.Add((ControlEventType.Default, handler));
205
206 return this;
207 }
208
214 private const double UiHeldWarningSeconds = 2.0;
215
220 private int _callbackDepth;
221
227 private int _callbackPending;
228
233 private const int CallbackBacklogWarning = 5;
234
239 private System.Threading.Tasks.Task _callbackChain = System.Threading.Tasks.Task.CompletedTask;
240
245 private readonly object _callbackChainGate = new object();
246
251 private string CallbackOwner => true == string.IsNullOrEmpty(Name) ? GetType().Name : Name;
252
258 private void EnterCallback()
259 {
260 if (1 < System.Threading.Interlocked.Increment(ref _callbackDepth))
261 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"The callback for [{CallbackOwner}] was entered while it was still running, which is what a handler writing to its own control does", this, GPALObjectType.Other);
262 }
263
271 private void RunOnUiThread(EventHandler handler, object sender, EventArgs args)
272 {
273 System.Diagnostics.Stopwatch held = System.Diagnostics.Stopwatch.StartNew();
274
275 EnterCallback();
276
277 try
278 {
279 handler(sender, args);
280 }
281 finally
282 {
283 // the depth comes back down even when the handler threw, or the next press is reported as a loop
284 System.Threading.Interlocked.Decrement(ref _callbackDepth);
285 }
286
287 if (UiHeldWarningSeconds < held.Elapsed.TotalSeconds)
288 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"The handler for [{CallbackOwner}] held the window for [{held.Elapsed.TotalSeconds:0.0}] seconds. Attach it with WithCallbackOffUIThread and it runs on a thread of its own", this, GPALObjectType.Other);
289 }
290
320 where TDelegate : Delegate
321 {
322 if (handler is EventHandler typed)
323 {
324 EventHandler onItsOwnThread = (sender, args) =>
325 {
326 if (CallbackBacklogWarning == System.Threading.Interlocked.Increment(ref _callbackPending))
327 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{CallbackBacklogWarning}] handlers for [{CallbackOwner}] are waiting their turn, so the events are arriving faster than the handler runs", this, GPALObjectType.Other);
328
329 lock (_callbackChainGate)
330 _callbackChain = _callbackChain.ContinueWith(previous => RunInBackground(typed, sender, args));
331 };
332
333 Callbacks.Add((ControlEventType.Default, onItsOwnThread));
334 }
335 else
336 {
337 // only EventHandler can be wrapped without knowing the signature, and attaching it on the UI
338 // thread is what it would have done anyway. saying so beats a form that quietly still freezes
339 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{typeof(TDelegate).Name}] cannot be run in the background, so the handler for [{CallbackOwner}] stays on the UI thread", this, GPALObjectType.Other);
340
341 Callbacks.Add((ControlEventType.Default, handler));
342 }
343
344 return this;
345 }
346
354 private void RunInBackground(EventHandler handler, object sender, EventArgs args)
355 {
356 EnterCallback();
357
358 try
359 {
360 handler(sender, args);
361 }
362 catch (Exception ex)
363 {
364 // nothing is waiting on this thread to catch it, and a handler that died in silence looks
365 // like a button that did nothing
366 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"A background handler for [{CallbackOwner}] ended in an exception", this, GPALObjectType.Other, ex);
367 }
368 finally
369 {
370 System.Threading.Interlocked.Decrement(ref _callbackDepth);
371 System.Threading.Interlocked.Decrement(ref _callbackPending);
372 }
373 }
374
382 public IAllowControlSettings ForEvent(ControlEventType eventType)
383 {
384 // replace the last "Default" entry with the explicit event
385 // (this is the only "magic" — keeps chain order natural)
386 if (Callbacks.Count > 0)
387 {
388 var last = Callbacks[Callbacks.Count - 1];
389 if (last.EventType == ControlEventType.Default)
390 {
391 Callbacks[Callbacks.Count - 1] = (eventType, last.Handler);
392 return this;
393 }
394 }
395
396 // if no prior Default > just add as explicit (rare misuse case)
397 Callbacks.Add((eventType, null)); // null handler = ignore later
398 return this;
399 }
400
405 public IAllowToolTipDelay WithToolTip(string toolTipText)
406 {
407 ToolTipText = toolTipText ?? "";
408 return this;
409 }
410
417 {
418 ToolTipAutoPopDelayMs = Math.Max(1000, delayInMs); // enforce reasonable minimum
419 return this;
420 }
421
436 public IAllowControlSettings WithShortcutKey(System.Windows.Forms.Keys shortCutKey)
437 {
438 ShortCutKey = shortCutKey;
439 return this;
440 }
441
451 internal static void OnUi(Control control, MethodInvoker work)
452 {
453 if (false == Alive(control))
454 return;
455
456 try
457 {
458 // no handle means no window to marshal to and no thread that owns it, so the work is done here
459 // and windows forms keeps it until the control is created
460 if (true == control.IsHandleCreated && true == control.InvokeRequired)
461 control.Invoke(work);
462 else
463 work();
464 }
465 catch (ObjectDisposedException)
466 {
467 // the form went between the check and the work
468 }
469 catch (InvalidOperationException)
470 {
471 // the handle went the same way, which Invoke reports as this
472 }
473 }
474
483 internal static void OnUiOrHere(Control control, MethodInvoker work)
484 {
485 if (true == Alive(control))
486 OnUi(control, work);
487 else
488 work();
489 }
490
498 internal static void PostToUi(Control control, MethodInvoker work)
499 {
500 if (false == Alive(control))
501 return;
502
503 try
504 {
505 if (true == control.IsHandleCreated && true == control.InvokeRequired)
506 control.BeginInvoke(work);
507 else
508 work();
509 }
510 catch (ObjectDisposedException)
511 {
512 // the form went between the check and the work
513 }
514 catch (InvalidOperationException)
515 {
516 // the handle went the same way, which BeginInvoke reports as this
517 }
518 }
519
530 internal static T OnUiRead<T>(Control control, Func<T> read, T whenGone)
531 {
532 T retVal = whenGone;
533
534 if (true == Alive(control))
535 try
536 {
537 retVal = true == control.IsHandleCreated && true == control.InvokeRequired
538 ? (T)control.Invoke(read)
539 : read();
540 }
541 catch (ObjectDisposedException)
542 {
543 // the form went between the check and the read
544 }
545 catch (InvalidOperationException)
546 {
547 // the handle went the same way, which Invoke reports as this
548 }
549
550 return retVal;
551 }
552
559 public void OnUiThread(Action action)
560 {
561 OnUiOrHere(WindowsControl as Control, () => action());
562 }
563
578 public IAllowControlSettings WithName(string name)
579 {
580 this.Name = name;
581 return this;
582 }
583
599 {
600 if (null != WindowsControl)
601 ((TextBox)WindowsControl).Tag = tag;
602 else
603 this.Tag = tag;
604 return this;
605 }
606
612 {
613 this.Height = height;
614 return this;
615 }
616
631 {
632 if (null != WindowsControl)
633 ((TextBox)WindowsControl).Text = text; // this.Text will be updated via the TextChanged event;
634 else
635 this.Text = text;
636 return this;
637 }
638
660 public dynamic ToGPALObject()
661 {
662 return this;
663 }
664 }
665
701 {
702 internal ChartColorPalette ChartColorPalette { get; set; }
703 internal Series mySeries { get; set; }
704 internal string Title { get; set; }
705
706 internal GPALChart()
707 {
708 ControlType = FormControlType.Chart;
709 Chart chart = new Chart
710 {
711 Name = Name,
712 AutoSize = false,
713 Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
714 Dock = DockStyle.Fill,
715 };
716 chart.ChartAreas.Add(new ChartArea(Name));
717 WindowsControl = chart;
718 }
724 public IAllowControlSettingsAndChartSettings WithPalette(ChartColorPalette colorPalette)
725 {
726 ((Chart)WindowsControl).Palette = colorPalette;
727 return this;
728 }
729
735 {
736 ((Chart)WindowsControl).Series.Add(series);
737 return this;
738 }
739
745 {
746 ((Chart)WindowsControl).Titles.Add(title);
747 return this;
748 }
749
752 public SeriesCollection Series
753 {
754 get
755 {
756 return ((Chart)(WindowsControl)).Series;
757 }
758 }
759
762 public LegendCollection Legends
763 {
764 get
765 {
766 return ((Chart)(WindowsControl)).Legends;
767 }
768 }
769
772 public TitleCollection Titles
773 {
774 get
775 {
776 return ((Chart)(WindowsControl)).Titles;
777 }
778 }
779
783 internal ChartAreaCollection ChartAreas
784 {
785 get
786 {
787 return ((Chart)WindowsControl).ChartAreas;
788 }
789 }
790 }
791
823 {
829 internal bool IsDefault { get; set; }
830 internal GPALButton()
831 {
832 ControlType = FormControlType.Button;
833 IsDefault = false;
834 }
839 public bool Enabled
840 {
841 get
842 {
843 if (null != ((Button)this.WindowsControl))
844 return ((Button)this.WindowsControl).Enabled;
845 else
846 return IsEnabled;
847 }
848 set
849 {
850 if (WindowsControl is Button btn && true == Alive(btn))
851 {
852 OnUi(btn, () => btn.Enabled = value);
853 }
854 else
855 IsEnabled = value;
856 }
857 }
858
864 {
865 get
866 {
867 IsDefault = true;
868 return this;
869 }
870 }
871
877 {
878 this.Enabled = enabled;
879 return this;
880 }
881 }
882
901 {
902 internal GPALCheckbox()
903 {
904 ControlType = FormControlType.Checkbox;
905 }
911 public IAllowControlSettings WithChecked(bool isChecked)
912 {
913 this.Checked = isChecked;
914 return this;
915 }
916
921 public bool Checked
922 {
923 get
924 {
925 bool retVal = IsChecked;
926
927 if (WindowsControl is CheckBox cb && true == Alive(cb))
928 retVal = OnUiRead(cb, () => cb.Checked, retVal);
929
930 return retVal;
931 }
932 set
933 {
934 if (WindowsControl is CheckBox cb && true == Alive(cb))
935 {
936 OnUi(cb, () => cb.Checked = value);
937 }
938 else
939 {
940 IsChecked = value;
941 }
942 }
943 }
944
948 public bool Enabled
949 {
950 get
951 {
952 if (null != ((CheckBox)this.WindowsControl))
953 return ((CheckBox)this.WindowsControl).Enabled;
954 else
955 return IsEnabled;
956 }
957 set
958 {
959 if (WindowsControl is CheckBox cbx && true == Alive(cbx))
960 {
961 OnUi(cbx, () => cbx.Enabled = value);
962 }
963 else
964 IsEnabled = value;
965 }
966 }
967
973 {
974 this.Enabled = enabled;
975 return this;
976 }
977 }
978
995 public class GPALDataGridView : GPALControl, IAllowDataGridViewControlSettings
996 {
997 private IGPALGrid<string> _grid;
998 private readonly List<string> _columnNames = new List<string>();
999 private bool _autoGenerateColumns = false; // default false – only generate if explicitly asked or no columns set
1000
1001 internal GPALDataGridView()
1002 {
1003 ControlType = FormControlType.DataGridView;
1004 }
1005
1012 public IAllowDataGridViewControlSettings WithColumns(params string[] columnNames)
1013 {
1014 if (columnNames != null)
1015 {
1016 _columnNames.AddRange(columnNames);
1017 }
1018 return this;
1019 }
1020
1027 {
1028 base.ReadOnly = readOnly;
1029 return this;
1030 }
1031
1039 {
1040 _autoGenerateColumns = autoGenerate;
1041 return this;
1042 }
1043
1050 public IAllowDataGridViewControlSettings WithGrid(IGPALGrid<string> grid)
1051 {
1052 this.CurrentGrid = grid;
1053 return this;
1054 }
1055
1064 internal void SyncGridToControl()
1065 {
1066 if (!(WindowsControl is DataGridView dgv) || false == Alive(dgv) || _grid == null)
1067 {
1068 return;
1069 }
1070
1071 if (dgv.InvokeRequired)
1072 {
1073 OnUi(dgv, SyncGridToControl);
1074 return;
1075 }
1076
1077 dgv.Rows.Clear();
1078 dgv.Columns.Clear();
1079
1080 // Determine column count from first non-null row (defensive)
1081 int columnCount = 0;
1082 foreach (List<string> row in _grid)
1083 {
1084 if (row != null)
1085 {
1086 columnCount = row.Count;
1087 break;
1088 }
1089 }
1090
1091 // Columns
1092 if (_columnNames.Count > 0)
1093 {
1094 int colIndex = 0;
1095 foreach (string header in _columnNames)
1096 {
1097 if (colIndex >= columnCount) break;
1098 dgv.Columns.Add(header, header);
1099 colIndex++;
1100 }
1101 }
1102 else if (_autoGenerateColumns && columnCount > 0)
1103 {
1104 for (int i = 0; i < columnCount; i++)
1105 {
1106 string header = $"Column {i + 1}";
1107 dgv.Columns.Add(header, header);
1108 }
1109 }
1110
1111 // Rows
1112 foreach (List<string> rowData in _grid)
1113 {
1114 if (rowData == null) continue;
1115
1116 var dgvRow = new DataGridViewRow();
1117 var cells = new DataGridViewCell[rowData.Count];
1118
1119 int colIndex = 0;
1120 foreach (string cellValue in rowData)
1121 {
1122 cells[colIndex] = new DataGridViewTextBoxCell
1123 {
1124 Value = cellValue ?? ""
1125 };
1126 colIndex++;
1127 }
1128
1129 dgvRow.Cells.AddRange(cells);
1130 dgv.Rows.Add(dgvRow);
1131 }
1132
1133 dgv.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
1134 dgv.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);
1135 }
1136
1141 public IGPALGrid<string> CurrentGrid
1142 {
1143 get => _grid;
1144 set
1145 {
1146 _grid = value;
1147 SyncGridToControl();
1148 }
1149 }
1150
1153 public IReadOnlyList<string> ColumnNames => _columnNames.AsReadOnly();
1158 public bool AutoGenerateColumns => _autoGenerateColumns;
1159 }
1160
1178 {
1179 internal bool InputIsPassword { get; set; } = false;
1180 internal bool IsReadOnly { get; set; } = false;
1181 internal string PlaceHolder { get; set; }
1182 internal GPALInput()
1183 {
1184 ControlType = FormControlType.Input;
1185 }
1190 public bool Enabled
1191 {
1192 get
1193 {
1194 if (null != ((PlaceholderTextBox)this.WindowsControl))
1195 return ((PlaceholderTextBox)this.WindowsControl).Enabled;
1196 else
1197 return IsEnabled;
1198 }
1199 set
1200 {
1201 if (WindowsControl is PlaceholderTextBox ptbInput && true == Alive(ptbInput))
1202 {
1203 OnUi(ptbInput, () => ptbInput.Enabled = value);
1204 }
1205 else
1206 IsEnabled = value;
1207 }
1208 }
1209
1215 {
1216 this.Enabled = enabled;
1217 return this;
1218 }
1219
1229 public bool ShowPassword
1230 {
1231 get
1232 {
1233 return false == InputIsPassword;
1234 }
1235 set
1236 {
1237 InputIsPassword = false == value;
1238
1239 if (WindowsControl is PlaceholderTextBox ptbInput && true == Alive(ptbInput))
1240 {
1241 char passwordChar = true == value ? '\0' : '●'; // bullet
1242
1243 OnUi(ptbInput, () => ptbInput.PasswordChar = passwordChar);
1244 }
1245 }
1246 }
1247
1248 public IAllowControlSettings IsPassword
1249 {
1250 get
1251 {
1252 InputIsPassword = true;
1253 if (true == String.IsNullOrEmpty(PlaceHolder))
1254 PlaceHolder = "Enter Password";
1255 return this;
1256 }
1257 }
1258 public IAllowControlSettings WithPlaceholder(string PlaceholderText)
1259 {
1260 PlaceHolder = PlaceholderText;
1261 if (WindowsControl is PlaceholderTextBox tb && true == Alive(tb))
1262 tb.PlaceholderText = PlaceholderText;
1263 return this;
1264 }
1265
1266 public IAllowControlSettings WithReadOnly(bool readOnly = true)
1267 {
1268 IsReadOnly = readOnly;
1269 if (WindowsControl is PlaceholderTextBox tb && true == Alive(tb))
1270 tb.ReadOnly = readOnly;
1271 return this;
1272 }
1273
1274 }
1275
1295 // TOFO: font type, font size
1297 {
1298 internal GPALLabel()
1299 {
1300 ControlType = FormControlType.Label;
1301 }
1302 }
1303
1328 // TODO: group box
1330 {
1331 internal GPALRadioButton()
1332 {
1333 ControlType = FormControlType.RadioButton;
1334 }
1340 public IAllowControlSettings WithChecked(bool isChecked)
1341 {
1342 this.Checked = isChecked;
1343 return this;
1344 }
1345
1350 public bool Checked
1351 {
1352 get
1353 {
1354 bool retVal = IsChecked;
1355
1356 if (WindowsControl is RadioButton rb && true == Alive(rb))
1357 retVal = OnUiRead(rb, () => rb.Checked, retVal);
1358
1359 return retVal;
1360 }
1361 set
1362 {
1363 if (WindowsControl is RadioButton rb && true == Alive(rb))
1364 {
1365 OnUi(rb, () => rb.Checked = value);
1366 }
1367 else
1368 {
1369 IsChecked = value;
1370 }
1371 }
1372 }
1373
1377 public bool Enabled
1378 {
1379 get
1380 {
1381 if (null != ((RadioButton)this.WindowsControl))
1382 return ((RadioButton)this.WindowsControl).Enabled;
1383 else
1384 return IsEnabled;
1385 }
1386 set
1387 {
1388 if (WindowsControl is RadioButton rb2 && true == Alive(rb2))
1389 {
1390 OnUi(rb2, () => rb2.Enabled = value);
1391 }
1392 else
1393 IsEnabled = value;
1394 }
1395 }
1396
1402 {
1403 this.Enabled = enabled;
1404 return this;
1405 }
1406 }
1407
1426 public class GPALTab : GPALControl, IAllowControlSettings
1427 {
1428 internal GPALTab()
1429 {
1430 ControlType = FormControlType.Tab;
1431 }
1436 public bool Enabled
1437 {
1438 get
1439 {
1440 // a GPALTab is one page, and its control is the TabPage. asking the TabControl would answer
1441 // for every tab at once
1442 return WindowsControl is TabPage page ? page.Enabled : IsEnabled;
1443 }
1444 set
1445 {
1446 IsEnabled = value;
1447
1448 if (!(WindowsControl is TabPage page) || false == Alive(page)) return;
1449
1450 OnUi(page, () => page.Enabled = value);
1451 }
1452 }
1453
1459 {
1460 this.Enabled = enabled;
1461 return this;
1462 }
1463
1466 public void Activate()
1467 {
1468 if (WindowsControl is TabPage tabPage && tabPage.Parent is TabControl tabControl)
1469 tabControl.SelectedTab = tabPage;
1470 }
1471 }
1472
1478 public class GPALSpliter
1479 {
1480 internal GPALSpliter() { }
1481 }
1482
1501 public class GPALSplitter : GPALControl, IAllowSplitterControlSettings
1502 {
1504 internal bool IsHorizontalSplit { get; private set; } = true;
1506 internal int Dimension { get; private set; }
1508 internal double Percentage { get; private set; }
1509 internal bool IsPercentage => 0 < Percentage;
1511 internal List<object> FormControls { get; } = new List<object>();
1512
1513 internal GPALSplitter() { }
1514
1526 {
1527 Height = height;
1528 return this;
1529 }
1530 public IAllowSplitterControlSettings HSplit(int leftWidth)
1531 {
1532 IsHorizontalSplit = true;
1533 Dimension = leftWidth;
1534 return this;
1535 }
1542 public IAllowSplitterControlSettings HSplit(double leftPercent)
1543 {
1544 IsHorizontalSplit = true;
1545 Percentage = leftPercent;
1546 return this;
1547 }
1548
1554 {
1555 IsHorizontalSplit = false;
1556 Dimension = topHeight;
1557 return this;
1558 }
1559
1565 public IAllowSplitterControlSettings VSplit(double topPercent)
1566 {
1567 IsHorizontalSplit = false;
1568 Percentage = topPercent;
1569 return this;
1570 }
1571
1577 public IAllowSplitterControlSettings WithFormControl(object gPALFormControl)
1578 {
1579 FormControls.Add(gPALFormControl);
1580 return this;
1581 }
1582
1586 public new GPALSplitter ToGPALObject()
1587 {
1588 return this;
1589 }
1590 }
1591
1602 public class GPALGroupBox : GPALControl, IAllowGroupBoxControlSettings
1603 {
1605 internal List<object> FormControls { get; } = new List<object>();
1606
1607 internal GPALGroupBox() { }
1608
1615 {
1616 Text = text;
1617 return this;
1618 }
1619
1624 public IAllowGroupBoxControlSettings WithFormControl(object gPALFormControl)
1625 {
1626 FormControls.Add(gPALFormControl);
1627 return this;
1628 }
1629
1633 public new GPALGroupBox ToGPALObject()
1634 {
1635 return this;
1636 }
1637 }
1638
1644 {
1645 internal GPALTableLayoutPanel()
1646 {
1647 ControlType = FormControlType.TableLayoutPanel;
1648 }
1653 public bool Enabled
1654 {
1655 get
1656 {
1657 if (null != ((TableLayoutPanel)this.WindowsControl))
1658 return ((TableLayoutPanel)this.WindowsControl).Enabled;
1659 else
1660 return IsEnabled;
1661 }
1662 set
1663 {
1664 if (null != ((TableLayoutPanel)this.WindowsControl))
1665 ((TableLayoutPanel)this.WindowsControl).Enabled = value;
1666 else
1667 IsEnabled = value;
1668 }
1669 }
1670
1675 public TableLayoutPanelGrowStyle GrowStyle
1676 {
1677 get
1678 {
1679 if (null != ((TableLayoutPanel)this.WindowsControl))
1680 return ((TableLayoutPanel)this.WindowsControl).GrowStyle;
1681 else
1682 return GrowStyle;
1683 }
1684 set
1685 {
1686 if (null != ((TableLayoutPanel)this.WindowsControl))
1687 ((TableLayoutPanel)this.WindowsControl).GrowStyle = value;
1688 else
1689 GrowStyle = value;
1690 }
1691 }
1692
1696 public int ColumnCount
1697 {
1698 get
1699 {
1700 if (null != ((TableLayoutPanel)this.WindowsControl))
1701 return ((TableLayoutPanel)this.WindowsControl).ColumnCount;
1702 else
1703 return ColumnCount;
1704 }
1705 set
1706 {
1707 if (null != ((TableLayoutPanel)this.WindowsControl))
1708 ((TableLayoutPanel)this.WindowsControl).ColumnCount = value;
1709 else
1710 ColumnCount = value;
1711 }
1712 }
1713
1718 public int RowCount
1719 {
1720 get
1721 {
1722 if (null != ((TableLayoutPanel)this.WindowsControl))
1723 return ((TableLayoutPanel)this.WindowsControl).RowCount;
1724 else
1725 return RowCount;
1726 }
1727 set
1728 {
1729 if (null != ((TableLayoutPanel)this.WindowsControl))
1730 ((TableLayoutPanel)this.WindowsControl).RowCount = value;
1731 else
1732 RowCount = value;
1733 }
1734 }
1735
1736
1743 {
1744 ColumnCount = columnCount;
1745 return this;
1746 }
1747
1754 {
1755 this.Enabled = enabled;
1756 return this;
1757 }
1758
1764 public IAllowControlSettingsAndTableLayoutPanelSettings WithGrowStyle(TableLayoutPanelGrowStyle tableLayoutPanelGrowStyle)
1765 {
1766 this.GrowStyle = tableLayoutPanelGrowStyle;
1767 return this;
1768 }
1769
1776 {
1777 ColumnCount = rowCount;
1778 return this;
1779 }
1780 }
1781
1799 {
1800 internal bool IsReadOnly { get; set; } = false;
1801 internal string PlaceHolder { get; set; }
1802
1803 // lines wait here until the thread that owns the control is free to take them. see AppendLine
1804 private readonly ConcurrentQueue<string> pendingLines = new ConcurrentQueue<string>();
1805 private readonly object appendGate = new object();
1806 private bool waitingForHandle = false;
1807
1808 internal GPALTextArea()
1809 {
1810 ControlType = FormControlType.TextArea;
1811 }
1816 public bool Enabled
1817 {
1818 get
1819 {
1820 if (null != ((PlaceholderTextBox)this.WindowsControl))
1821 return ((PlaceholderTextBox)this.WindowsControl).Enabled;
1822 else
1823 return IsEnabled;
1824 }
1825 set
1826 {
1827 if (WindowsControl is PlaceholderTextBox ptbArea && true == Alive(ptbArea))
1828 {
1829 OnUi(ptbArea, () => ptbArea.Enabled = value);
1830 }
1831 else
1832 IsEnabled = value;
1833 }
1834 }
1835
1841 {
1842 this.Enabled = enabled;
1843 return this;
1844 }
1845
1846 public IAllowControlSettings WithPlaceholder(string PlaceholderText)
1847 {
1848 PlaceHolder = PlaceholderText;
1849 if (WindowsControl is PlaceholderTextBox tb && true == Alive(tb))
1850 tb.PlaceholderText = PlaceholderText;
1851 return this;
1852 }
1853
1854 public IAllowControlSettings WithReadOnly(bool readOnly = true)
1855 {
1856 IsReadOnly = readOnly;
1857 if (WindowsControl is PlaceholderTextBox tb && true == Alive(tb))
1858 tb.ReadOnly = readOnly;
1859 return this;
1860 }
1861
1869 public void AppendLine(string text, int maxLines = 0)
1870 {
1871 // queued first and written from nowhere but the ui thread. AppendLine is called on whichever thread
1872 // published, and during a workflow that is a worker while the ui thread waits inside Run for it to
1873 // finish. A control belongs to the thread that made it, and asking that thread for anything while it
1874 // is blocked is where the run stops and never starts again
1875 pendingLines.Enqueue(text);
1876
1877 Drain(maxLines);
1878 }
1879
1886 private void Drain(int maxLines)
1887 {
1888 // a form on its way out still publishes, and posting to a control whose handle is gone makes windows
1889 // forms try to build another one. nothing to append to is not a failure, it is a closed form
1890 if (!(WindowsControl is PlaceholderTextBox tb) || false == Alive(tb)) return;
1891
1892 // no handle means one of two opposite things: built and not yet shown, or shown and now closed.
1893 // disposal tells them apart, and the guard above already dealt with the closed one, so what is left is
1894 // a control waiting to be created, a tab nobody has opened. the lines wait in the queue, and the
1895 // control says when it has a handle rather than being touched from here to find out
1896 if (false == tb.IsHandleCreated)
1897 {
1898 lock (appendGate)
1899 {
1900 if (false == waitingForHandle)
1901 {
1902 waitingForHandle = true;
1903 tb.HandleCreated += (sender, args) => Drain(maxLines);
1904 }
1905 }
1906
1907 return;
1908 }
1909
1910 MethodInvoker drain = () =>
1911 {
1912 // posted while the handle was there and run after it went. appending now rebuilds the handle
1913 if (false == Alive(tb) || false == tb.IsHandleCreated) return;
1914
1915 while (true == pendingLines.TryDequeue(out string line))
1916 {
1917 if (maxLines > 0 && tb.Lines.Length >= maxLines)
1918 {
1919 int keep = maxLines - 1;
1920 string[] trimmed = new string[keep];
1921 Array.Copy(tb.Lines, tb.Lines.Length - keep, trimmed, 0, keep);
1922 tb.Text = string.Join("\r\n", trimmed);
1923 }
1924 if (tb.TextLength > 0) tb.AppendText("\r\n");
1925 tb.AppendText(line);
1926 }
1927
1928 tb.ScrollToCaret();
1929 };
1930 // posted rather than waited on. a workflow raised from a button runs on the ui thread, so anything
1931 // publishing from a worker thread while that runs would block here for as long as the workflow takes,
1932 // and the workflow is waiting for the same thread. the lines land when the ui is free again
1933 PostToUi(tb, drain);
1934 }
1935
1941 public void Clear()
1942 {
1943 // what has not been shown yet is cleared as well, or a queue built up while a run held the ui thread
1944 // arrives afterwards and fills a box that was just emptied
1945 while (true == pendingLines.TryDequeue(out string dropped)) { }
1946
1947 if (!(WindowsControl is PlaceholderTextBox tb) || false == Alive(tb)) return;
1948
1949 if (false == tb.IsHandleCreated)
1950 {
1951 tb.Text = string.Empty;
1952 return;
1953 }
1954
1955 MethodInvoker clear = () =>
1956 {
1957 if (false == Alive(tb) || false == tb.IsHandleCreated) return;
1958
1959 tb.Clear();
1960 };
1961
1962 PostToUi(tb, clear);
1963 }
1964
1968 public void ScrollToEnd()
1969 {
1970 if (!(WindowsControl is PlaceholderTextBox tb) || false == Alive(tb)) return;
1971 if (false == Alive(tb)) return;
1972
1973 PostToUi(tb, () => tb.ScrollToCaret());
1974 }
1975 }
1976
1991 public class GPALRichTextBox : GPALControl, IAllowRichTextBoxControlSettings
1992 {
1993 private string _text = string.Empty;
1994 internal bool IsReadOnly { get; set; } = true;
1995
1996 internal GPALRichTextBox()
1997 {
1998 ControlType = FormControlType.RichTextBox;
1999 }
2000
2006 public new string Text
2007 {
2008 get => _text;
2009 set
2010 {
2011 _text = value ?? string.Empty;
2012 if (WindowsControl is RichTextBox rtb && true == Alive(rtb))
2013 {
2014 OnUi(rtb, () => RenderMarkdown(rtb, _text));
2015 }
2016 }
2017 }
2018
2025 {
2026 this.Text = text;
2027 return this;
2028 }
2029
2034 public bool Enabled
2035 {
2036 get
2037 {
2038 if (WindowsControl is RichTextBox rtb && true == Alive(rtb))
2039 return rtb.Enabled;
2040 else
2041 return IsEnabled;
2042 }
2043 set
2044 {
2045 if (WindowsControl is RichTextBox rtb && true == Alive(rtb))
2046 {
2047 OnUi(rtb, () => rtb.Enabled = value);
2048 }
2049 else
2050 {
2051 IsEnabled = value;
2052 }
2053 }
2054 }
2055
2061 public IAllowControlSettings WithEnabled(bool enabled = true)
2062 {
2063 this.Enabled = enabled;
2064 return this;
2065 }
2066
2067 public IAllowControlSettings WithReadOnly(bool readOnly = true)
2068 {
2069 IsReadOnly = readOnly;
2070 if (WindowsControl is RichTextBox rtb && true == Alive(rtb))
2071 rtb.ReadOnly = readOnly;
2072 return this;
2073 }
2074
2075 // matches **bold**, *italic*, __underline__, ~~strikethrough~~, `code`, {colorname}text{} — bold before italic to avoid ** ambiguity
2076 private static readonly Regex _inlinePattern = new Regex(
2077 @"\*\*(?<b>.+?)\*\*|\*(?<i>.+?)\*|__(?<u>.+?)__|~~(?<s>.+?)~~|`(?<code>.+?)`|{(?<c>[^}]+)}(?<ct>.+?)\{\}",
2078 RegexOptions.Compiled);
2079
2088 internal static void RenderMarkdown(RichTextBox rtb, string markdown)
2089 {
2090 rtb.Clear();
2091
2092 Font baseFont = rtb.Font;
2093 Color baseColor = rtb.ForeColor;
2094 FontFamily codeFamily = new FontFamily("Courier New");
2095 string[] lines = (markdown ?? string.Empty).Replace("\r\n", "\n").Split('\n');
2096
2097 foreach (string rawLine in lines)
2098 {
2099 string line = rawLine;
2100
2101 int headingLevel = 0;
2102 while (headingLevel < line.Length && line[headingLevel] == '#')
2103 headingLevel++;
2104 if (headingLevel > 0 && headingLevel <= 3 && headingLevel < line.Length && line[headingLevel] == ' ')
2105 line = line.Substring(headingLevel + 1);
2106 else
2107 headingLevel = 0;
2108
2109 int leadingSpaces = 0;
2110 while (leadingSpaces < line.Length && line[leadingSpaces] == ' ')
2111 leadingSpaces++;
2112 int indentLevel = leadingSpaces / 2;
2113 string trimmed = line.Substring(leadingSpaces);
2114
2115 // horizontal rule
2116 if (trimmed == "---")
2117 {
2118 rtb.SelectionStart = rtb.TextLength;
2119 rtb.SelectionLength = 0;
2120 rtb.SelectionFont = new Font(baseFont.FontFamily, baseFont.Size, FontStyle.Regular);
2121 rtb.SelectionColor = Color.Gray;
2122 rtb.AppendText(new string('-', 48) + "\n");
2123 continue;
2124 }
2125
2126 bool isBulletItem = false;
2127 bool isListItem = false;
2128 if (trimmed.StartsWith("- ") || trimmed.StartsWith("* "))
2129 {
2130 isBulletItem = true;
2131 isListItem = true;
2132 trimmed = trimmed.Substring(2);
2133 }
2134 else
2135 {
2136 Match numberedMatch = Regex.Match(trimmed, @"^(\d+\.\s+)(.*)$");
2137 if (numberedMatch.Success)
2138 {
2139 isListItem = true;
2140 trimmed = numberedMatch.Groups[1].Value + numberedMatch.Groups[2].Value;
2141 }
2142 }
2143
2144 int lineStart = rtb.TextLength;
2145
2146 FontStyle headingBase = headingLevel > 0 ? FontStyle.Bold : FontStyle.Regular;
2147 float headingSize = headingLevel > 0 ? baseFont.Size + (4 - headingLevel) * 2 : baseFont.Size;
2148
2149 foreach ((string segmentText, FontStyle style, Color? color, bool isCode) in SplitInline(trimmed, headingBase))
2150 {
2151 rtb.SelectionStart = rtb.TextLength;
2152 rtb.SelectionLength = 0;
2153 rtb.SelectionFont = isCode
2154 ? new Font(codeFamily, baseFont.Size, FontStyle.Regular)
2155 : new Font(baseFont.FontFamily, headingSize, style);
2156 rtb.SelectionColor = isCode ? Color.SteelBlue : (color ?? baseColor);
2157 rtb.AppendText(segmentText);
2158 }
2159 rtb.AppendText("\n");
2160
2161 if (isListItem)
2162 {
2163 rtb.SelectionStart = lineStart;
2164 rtb.SelectionLength = rtb.TextLength - lineStart;
2165 rtb.SelectionBullet = isBulletItem;
2166 rtb.SelectionIndent = 20 * (indentLevel + 1);
2167 }
2168 }
2169
2170 rtb.SelectionStart = 0;
2171 rtb.SelectionLength = 0;
2172 }
2173
2180 private static List<(string Text, FontStyle Style, Color? Color, bool IsCode)> SplitInline(string line, FontStyle inheritBase = FontStyle.Regular)
2181 {
2182 var result = new List<(string Text, FontStyle Style, Color? Color, bool IsCode)>();
2183 int pos = 0;
2184
2185 foreach (Match m in _inlinePattern.Matches(line))
2186 {
2187 if (m.Index > pos)
2188 result.Add((line.Substring(pos, m.Index - pos), inheritBase, null, false));
2189
2190 if (m.Groups["b"].Success)
2191 result.Add((m.Groups["b"].Value, inheritBase | FontStyle.Bold, null, false));
2192 else if (m.Groups["i"].Success)
2193 result.Add((m.Groups["i"].Value, inheritBase | FontStyle.Italic, null, false));
2194 else if (m.Groups["u"].Success)
2195 result.Add((m.Groups["u"].Value, inheritBase | FontStyle.Underline, null, false));
2196 else if (m.Groups["s"].Success)
2197 result.Add((m.Groups["s"].Value, inheritBase | FontStyle.Strikeout, null, false));
2198 else if (m.Groups["code"].Success)
2199 result.Add((m.Groups["code"].Value, FontStyle.Regular, null, true));
2200 else if (m.Groups["c"].Success)
2201 {
2202 Color named = Color.FromName(m.Groups["c"].Value);
2203 Color? resolved = named.IsKnownColor || named.A > 0 ? (Color?)named : null;
2204 // recurse so bold/italic/underline inside a color span still render correctly
2205 foreach (var inner in SplitInline(m.Groups["ct"].Value, inheritBase))
2206 result.Add((inner.Item1, inner.Item2, inner.Item3 ?? resolved, inner.Item4));
2207 }
2208
2209 pos = m.Index + m.Length;
2210 }
2211
2212 if (pos < line.Length)
2213 result.Add((line.Substring(pos), inheritBase, null, false));
2214 if (result.Count == 0)
2215 result.Add((string.Empty, inheritBase, null, false));
2216
2217 return result;
2218 }
2219 }
2220
2237 public class GPALComboBox : GPALControl, IAllowComboBoxControlSettings
2238 {
2239 internal readonly List<string> items = new List<string>();
2240 internal int selectedIndex = -1;
2241 internal string selectedItem = null;
2242
2243 // items that stay on the list but cannot be chosen. an option that does not apply is worth showing
2244 // greyed, because a list that silently loses an entry reads as a list that never had one
2245 internal readonly HashSet<string> disabledItems = new HashSet<string>();
2246
2247 // what to fall back to when a disabled item is picked, so the box never sits on something invalid
2248 internal int lastEnabledIndex = -1;
2249
2250 internal GPALComboBox()
2251 {
2252 ControlType = FormControlType.ComboBox;
2253 }
2254
2260 public IAllowComboBoxControlSettings WithItems(params string[] itemArray)
2261 {
2262 if (null != itemArray)
2263 {
2264 items.AddRange(itemArray);
2265 ShowItems(itemArray);
2266 }
2267 return this;
2268 }
2269
2270 // the list a form was built with is one thing and the list on screen is another. adding items before
2271 // the form is shown only has the first to add to; adding them after has both, and a dropdown that does
2272 // not change is the whole reason a list would look stale
2273 private void ShowItems(IEnumerable<string> itemsToShow)
2274 {
2275 if (!(WindowsControl is ComboBox cb) || false == Alive(cb)) return;
2276
2277 object[] adding = itemsToShow.Cast<object>().ToArray();
2278
2279 OnUi(cb, () => cb.Items.AddRange(adding));
2280 }
2281
2288 public void DisableItem(string item)
2289 {
2290 if (true == string.IsNullOrEmpty(item)) return;
2291
2292 disabledItems.Add(item);
2293 Redraw();
2294 }
2295
2300 public void EnableItem(string item)
2301 {
2302 if (true == string.IsNullOrEmpty(item)) return;
2303
2304 disabledItems.Remove(item);
2305 Redraw();
2306 }
2307
2313 public bool IsItemEnabled(string item)
2314 {
2315 return false == disabledItems.Contains(item);
2316 }
2317
2318 private void Redraw()
2319 {
2320 if (!(WindowsControl is ComboBox cb) || false == Alive(cb)) return;
2321
2322 OnUi(cb, () => cb.Invalidate());
2323 }
2324
2329 public void ClearItems()
2330 {
2331 items.Clear();
2332 disabledItems.Clear();
2333 selectedIndex = -1;
2334 selectedItem = null;
2335 lastEnabledIndex = -1;
2336
2337 if (!(WindowsControl is ComboBox cb) || false == Alive(cb)) return;
2338
2339 OnUi(cb, () => cb.Items.Clear());
2340 }
2341
2347 public IAllowComboBoxControlSettings WithItems(IEnumerable<string> itemCollection)
2348 {
2349 if (null != itemCollection)
2350 {
2351 items.AddRange(itemCollection);
2352 }
2353 return this;
2354 }
2355
2363 {
2364 if (null != itemArray)
2365 {
2366 foreach (object item in itemArray)
2367 items.Add(item?.ToString() ?? string.Empty);
2368 }
2369 return this;
2370 }
2371
2379 {
2380 selectedIndex = index;
2381 selectedItem = null;
2382 return this;
2383 }
2384
2392 {
2393 selectedItem = name;
2394 selectedIndex = -1;
2395 return this;
2396 }
2397
2402 public string SelectedValue
2403 {
2404 get
2405 {
2406 int idx = SelectedIndex;
2407 if ((0 > idx) || (idx >= items.Count))
2408 return null;
2409 return items[idx];
2410 }
2411 }
2412
2423 public bool Enabled
2424 {
2425 get
2426 {
2427 bool retVal = IsEnabled;
2428
2429 if (WindowsControl is ComboBox cb && true == Alive(cb))
2430 retVal = OnUiRead(cb, () => cb.Enabled, retVal);
2431
2432 return retVal;
2433 }
2434 set
2435 {
2436 if (WindowsControl is ComboBox cb && true == Alive(cb))
2437 {
2438 OnUi(cb, () => cb.Enabled = value);
2439 }
2440 else
2441 {
2442 IsEnabled = value;
2443 }
2444 }
2445 }
2446
2447 public int SelectedIndex
2448 {
2449 // read on the ui thread when asked from another one, the way every setter here already writes on it.
2450 // a workflow running off the ui thread is the normal case, not an odd one
2451 get
2452 {
2453 int retVal = selectedIndex;
2454
2455 if (WindowsControl is ComboBox cb && true == Alive(cb))
2456 retVal = OnUiRead(cb, () => cb.SelectedIndex, retVal);
2457
2458 return retVal;
2459 }
2460 set
2461 {
2462 if ((0 > value) || (value >= items.Count)) return;
2463
2464 selectedIndex = value;
2465 if (WindowsControl is ComboBox cb && true == Alive(cb))
2466 {
2467 OnUi(cb, () => cb.SelectedIndex = value);
2468 }
2469 }
2470 }
2471 }
2472
2488 public class GPALProgressBar : GPALControl, IAllowProgressBarControlSettings
2489 {
2490 private int _value = 0;
2491 private int _minimum = 0;
2492 private int _maximum = 100;
2493 private ProgressBarStyle _style = ProgressBarStyle.Blocks;
2494
2495 internal GPALProgressBar()
2496 {
2497 ControlType = FormControlType.ProgressBar;
2498 }
2499
2507 {
2508 this.Value = value;
2509 return this;
2510 }
2511
2518 {
2519 _minimum = min;
2520 if (_value < min) _value = min;
2521 SyncValueToControl();
2522 return this;
2523 }
2524
2531 {
2532 _maximum = max;
2533 if (_value > max) _value = max;
2534 SyncValueToControl();
2535 return this;
2536 }
2537
2543 public IAllowProgressBarControlSettings WithStyle(ProgressBarStyle style)
2544 {
2545 this.Style = style;
2546 return this;
2547 }
2548
2554 public void Increment(int step = 1)
2555 {
2556 this.Value += step;
2557 }
2558
2564 private void SyncValueToControl()
2565 {
2566 if (WindowsControl is ProgressBar pb && true == Alive(pb))
2567 {
2568 OnUi(pb, () => Apply(pb));
2569 }
2570 }
2571
2579 private void Apply(ProgressBar pb)
2580 {
2581 // maximum first when it is going up, minimum first when it is coming down, because the control
2582 // refuses a minimum above its own maximum and a maximum below its own minimum
2583 if (pb.Maximum < _maximum)
2584 {
2585 pb.Maximum = _maximum;
2586 pb.Minimum = _minimum;
2587 }
2588 else
2589 {
2590 pb.Minimum = _minimum;
2591 pb.Maximum = _maximum;
2592 }
2593
2594 pb.Value = _value;
2595 }
2596
2602 private void SyncStyleToControl()
2603 {
2604 if (WindowsControl is ProgressBar pb && true == Alive(pb))
2605 {
2606 OnUi(pb, () => pb.Style = _style);
2607 }
2608 }
2609
2615 public int Value
2616 {
2617 get => _value;
2618 set
2619 {
2620 _value = Math.Max(_minimum, Math.Min(_maximum, value));
2621 SyncValueToControl();
2622 }
2623 }
2624
2627 public int Minimum => _minimum;
2631 public int Maximum => _maximum;
2636 public ProgressBarStyle Style
2637 {
2638 get => _style;
2639 set
2640 {
2641 _style = value;
2642 SyncStyleToControl();
2643 }
2644 }
2645 }
2646
2660 public class GPALStatusStrip : GPALControl, IAllowStatusStripControlSettings
2661 {
2662 private string _text = "";
2663
2664 internal GPALStatusStrip()
2665 {
2666 ControlType = FormControlType.StatusStrip;
2667 }
2668
2674 public new string Text
2675 {
2676 get => _text;
2677 set
2678 {
2679 _text = value ?? "";
2680 SyncTextToControl();
2681 }
2682 }
2683
2690 {
2691 this.Text = text;
2692 return this;
2693 }
2694
2701 private void SyncTextToControl()
2702 {
2703 if (WindowsControl is StatusStrip strip && true == Alive(strip) && strip.Items.Count > 0)
2704 {
2705 ToolStripStatusLabel label = strip.Items[0] as ToolStripStatusLabel;
2706 if (label != null)
2707 {
2708 OnUi(strip, () => label.Text = _text);
2709 }
2710 }
2711 }
2712 }
2713
2731 public class GPALBarItem : GPALControl, IAllowBarItemSettings
2732 {
2733 internal System.Drawing.Image Image { get; set; }
2734
2735 internal GPALBarItem()
2736 {
2737 ControlType = FormControlType.BarItem;
2738 }
2739
2747 public IAllowBarItemSettings WithEnabled(bool trueFalse = true)
2748 {
2749 Enabled = trueFalse;
2750 return this;
2751 }
2752
2757 public bool Enabled
2758 {
2759 get
2760 {
2761 return WindowsControl is ToolStripItem strip ? strip.Enabled : IsEnabled;
2762 }
2763 set
2764 {
2765 IsEnabled = value;
2766
2767 if (!(WindowsControl is ToolStripItem strip)) return;
2768
2769 // a ToolStripItem is not a Control and has no InvokeRequired of its own. the strip that owns
2770 // it is one, and that is what has to be asked whether this is the right thread
2771 Control owner = strip.Owner;
2772
2773 OnUiOrHere(owner, () => strip.Enabled = value);
2774 }
2775 }
2776
2782 public new IAllowBarItemSettings WithText(string text)
2783 {
2784 this.Text = text;
2785 return this;
2786 }
2787
2793 public IAllowBarItemSettings WithImage(System.Drawing.Image image)
2794 {
2795 this.Image = image;
2796 return this;
2797 }
2798
2805 {
2806 try
2807 {
2808 string path = file?.Filenames?.FirstOrDefault() ?? (string)file;
2809 if (!string.IsNullOrWhiteSpace(path) && System.IO.File.Exists(path))
2810 this.Image = System.Drawing.Image.FromFile(path);
2811 else
2812 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Image file not found for bar item [{path}]", this, GPALObjectType.Other);
2813 }
2814 catch (Exception ex)
2815 {
2816 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to load bar item image [{file?.Filename}]", this, GPALObjectType.Other, ex);
2817 }
2818 return this;
2819 }
2820 }
2821
2840 public class GPALMenuBar : GPALControl, IAllowMenuBarControlSettings
2841 {
2842 private string _currentMenu;
2843 internal readonly List<(string TopMenu, GPALBarItem Item, bool IsSeparator)> menuEntries
2844 = new List<(string, GPALBarItem, bool)>();
2845
2846 internal GPALMenuBar()
2847 {
2848 ControlType = FormControlType.MenuBar;
2849 }
2850
2858 {
2859 _currentMenu = topMenu;
2860 return this;
2861 }
2862
2869 {
2870 menuEntries.Add((_currentMenu, item, false));
2871 return this;
2872 }
2873
2879 {
2880 menuEntries.Add((_currentMenu, null, true));
2881 return this;
2882 }
2883 }
2884
2899 public class GPALToolbar : GPALControl, IAllowToolbarControlSettings
2900 {
2901 internal readonly List<(GPALBarItem Item, bool IsSeparator)> toolbarEntries
2902 = new List<(GPALBarItem, bool)>();
2903
2904 internal GPALToolbar()
2905 {
2906 ControlType = FormControlType.Toolbar;
2907 }
2908
2915 {
2916 toolbarEntries.Add((item, false));
2917 return this;
2918 }
2919
2925 {
2926 toolbarEntries.Add((null, true));
2927 return this;
2928 }
2929 }
2930
2945 public class GPALListView : GPALControl, IAllowListVIewControlSettings
2946 {
2947 private readonly List<string> columnHeaders = new List<string>();
2948 private readonly List<double> columnWeights = new List<double>();
2949 private ListViewMode viewMode = ListViewMode.Details;
2950 internal bool checkBoxes = false;
2951 private bool fullRowSelect = true;
2952 private bool multiSelect = false;
2953 private bool sortable = false;
2954
2955 internal GPALListView()
2956 {
2957 ControlType = FormControlType.ListView;
2958 }
2959
2972 {
2973 CheckBoxes = enable;
2974 return this;
2975 }
2976
2980 public bool CheckBoxes
2981 {
2982 get
2983 {
2984 return WindowsControl is ListView lv ? lv.CheckBoxes : checkBoxes;
2985 }
2986 set
2987 {
2988 checkBoxes = value;
2989
2990 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return;
2991
2992 OnUi(lv, () => lv.CheckBoxes = value);
2993 }
2994 }
2995
2996 public IAllowListVIewControlSettings WithColumns(params string[] headers)
2997 {
2998 if (headers != null)
2999 {
3000 columnHeaders.AddRange(headers);
3001 }
3002 return this;
3003 }
3004
3010 public IAllowListVIewControlSettings WithView(ListViewMode view)
3011 {
3012 viewMode = view;
3013 return this;
3014 }
3015
3022 {
3023 fullRowSelect = enable;
3024 return this;
3025 }
3026
3033 {
3034 multiSelect = enable;
3035 return this;
3036 }
3037
3041 public IAllowListVIewControlSettings WithSortable(bool sortable = true)
3042 {
3043 this.sortable = sortable;
3044 return this;
3045 }
3046
3052 {
3053 columnWeights.Add(weight);
3054 return this;
3055 }
3056
3060 public IReadOnlyList<string> Columns => columnHeaders.AsReadOnly();
3061
3065 public IReadOnlyList<double> ColumnWeights => columnWeights.AsReadOnly();
3066
3068 public bool Sortable => sortable;
3072 public View ViewMode
3073 {
3074 get
3075 {
3076 switch (viewMode)
3077 {
3078 case ListViewMode.List: return View.List;
3079 case ListViewMode.LargeIcon: return View.LargeIcon;
3080 case ListViewMode.SmallIcon: return View.SmallIcon;
3081 case ListViewMode.Tile: return View.Tile;
3082 default: return View.Details;
3083 }
3084 }
3085 }
3086
3089 public bool FullRowSelect => fullRowSelect;
3093 public bool MultiSelect => multiSelect;
3094
3095 public int SelectedIndex
3096 {
3097 get
3098 {
3099 int retVal = -1;
3100
3101 if (WindowsControl is ListView lv && true == Alive(lv))
3102 retVal = OnUiRead(lv, () => 0 < lv.SelectedItems.Count ? lv.SelectedItems[0].Index : -1, retVal);
3103
3104 return retVal;
3105 }
3106 }
3107
3108 public void ClearItems()
3109 {
3110 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return;
3111 OnUi(lv, () => lv.Items.Clear());
3112 }
3113
3119 public void DisableItem(int index)
3120 {
3121 SetItemEnabled(index, false);
3122 }
3123
3128 public void EnableItem(int index)
3129 {
3130 SetItemEnabled(index, true);
3131 }
3132
3138 public bool IsItemEnabled(int index)
3139 {
3140 bool retVal = true;
3141
3142 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return retVal;
3143
3144 MethodInvoker read = () =>
3145 {
3146 if (0 <= index && index < lv.Items.Count)
3147 retVal = SystemColors.GrayText != lv.Items[index].ForeColor;
3148 };
3149
3150 OnUi(lv, read);
3151
3152 return retVal;
3153 }
3154
3155 // the colour is the state, so what a row looks like and what it does can never disagree
3156 private void SetItemEnabled(int index, bool enabled)
3157 {
3158 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return;
3159
3160 MethodInvoker paint = () =>
3161 {
3162 if (0 > index || index >= lv.Items.Count) return;
3163
3164 lv.Items[index].ForeColor = true == enabled ? SystemColors.WindowText : SystemColors.GrayText;
3165
3166 if (false == enabled) lv.Items[index].Selected = false;
3167 };
3168
3169 OnUi(lv, paint);
3170 }
3171
3172 public void AddItem(string title, params string[] subItems)
3173 {
3174 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return;
3175 var item = new ListViewItem(title);
3176 foreach (var s in subItems) item.SubItems.Add(s);
3177 OnUi(lv, () => lv.Items.Add(item));
3178 }
3179
3180 public void SetColumnWidth(int index, int width)
3181 {
3182 if (!(WindowsControl is ListView lv) || false == Alive(lv)) return;
3183 if (index < 0 || index >= lv.Columns.Count) return;
3184 OnUi(lv, () => lv.Columns[index].Width = width);
3185 }
3186
3190 public string SelectedText =>
3191 WindowsControl is ListView lv2 && lv2.SelectedItems.Count > 0
3192 ? lv2.SelectedItems[0].Text
3193 : null;
3194 }
3195
3208 public class GPALNumericUpDown : GPALControl, IAllowNumericUpDownControlSettings
3209 {
3210 private decimal _value = 0m;
3211 private decimal _minimum = 0m;
3212 private decimal _maximum = 100m;
3213 private decimal _increment = 1m;
3214 private int _decimalPlaces = 0;
3215
3216 internal GPALNumericUpDown()
3217 {
3218 ControlType = FormControlType.NumericUpDown;
3219 }
3220
3227 {
3228 _value = value;
3229 return this;
3230 }
3231
3238 {
3239 _minimum = min;
3240 return this;
3241 }
3242
3249 {
3250 _maximum = max;
3251 return this;
3252 }
3253
3260 {
3261 _increment = inc;
3262 return this;
3263 }
3264
3271 {
3272 _decimalPlaces = places;
3273 return this;
3274 }
3275
3281 public decimal Value
3282 {
3283 get
3284 {
3285 decimal retVal = _value;
3286
3287 if (WindowsControl is NumericUpDown nud && true == Alive(nud))
3288 retVal = OnUiRead(nud, () => nud.Value, retVal);
3289
3290 return retVal;
3291 }
3292 set
3293 {
3294 if (WindowsControl is NumericUpDown nud && true == Alive(nud))
3295 {
3296 OnUi(nud, () => nud.Value = value);
3297 }
3298 else
3299 {
3300 _value = value;
3301 }
3302 }
3303 }
3304
3307 public decimal Minimum => _minimum;
3311 public decimal Maximum => _maximum;
3315 public decimal Increment => _increment;
3319 public int DecimalPlaces => _decimalPlaces;
3320 }
3321
3332 public class GPALDateTimePicker : GPALControl, IAllowDateTimePickerControlSettings
3333 {
3334 private DateTime _value = DateTime.Now;
3335 private DateTimePickerFormat _format = DateTimePickerFormat.Long;
3336 private string _customFormat = "";
3337
3338 internal GPALDateTimePicker()
3339 {
3340 ControlType = FormControlType.DateTimePicker;
3341 }
3342
3349 {
3350 _value = value;
3351 return this;
3352 }
3353
3361 public IAllowDateTimePickerControlSettings WithFormat(DateTimePickerFormat format)
3362 {
3363 _format = format;
3364 return this;
3365 }
3366
3373 {
3374 _customFormat = formatString;
3375 return this;
3376 }
3377
3383 public DateTime Value
3384 {
3385 get
3386 {
3387 DateTime retVal = _value;
3388
3389 if (WindowsControl is DateTimePicker dtp && true == Alive(dtp))
3390 retVal = OnUiRead(dtp, () => dtp.Value, retVal);
3391
3392 return retVal;
3393 }
3394 set
3395 {
3396 if (WindowsControl is DateTimePicker dtp && true == Alive(dtp))
3397 {
3398 OnUi(dtp, () => dtp.Value = value);
3399 }
3400 else
3401 {
3402 _value = value;
3403 }
3404 }
3405 }
3406
3409 public DateTimePickerFormat Format => _format;
3413 public string CustomFormat => _customFormat;
3414 }
3415
3425 public class GPALTreeView : GPALControl, IAllowTreeViewControlSettings
3426 {
3427 private bool _checkBoxes = false;
3428 private bool _showLines = true;
3429 private bool _showPlusMinus = true;
3430
3431 internal GPALTreeView()
3432 {
3433 ControlType = FormControlType.TreeView;
3434 }
3435
3442 {
3443 _checkBoxes = enable;
3444 return this;
3445 }
3446
3453 {
3454 _showLines = show;
3455 return this;
3456 }
3457
3464 {
3465 _showPlusMinus = show;
3466 return this;
3467 }
3468
3472 public bool CheckBoxes => _checkBoxes;
3476 public bool ShowLines => _showLines;
3480 public bool ShowPlusMinus => _showPlusMinus;
3481
3485 public void ClearNodes()
3486 {
3487 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return;
3488 OnUi(tv, () => tv.Nodes.Clear());
3489 }
3490
3496 {
3497 get
3498 {
3499 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return null;
3500
3501 TreeNode node = null;
3502
3503 OnUi(tv, () => node = tv.SelectedNode);
3504
3505 return null != node ? new GPALTreeNode(node) : null;
3506 }
3507 set
3508 {
3509 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return;
3510
3511 MethodInvoker select = () => tv.SelectedNode = value?.Node;
3512
3513 OnUi(tv, select);
3514 }
3515 }
3516
3521 public void BeginUpdate()
3522 {
3523 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return;
3524
3525 OnUi(tv, () => tv.BeginUpdate());
3526 }
3527
3531 public void EndUpdate()
3532 {
3533 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return;
3534
3535 OnUi(tv, () => tv.EndUpdate());
3536 }
3537
3543 public GPALTreeNode AddNode(string text)
3544 {
3545 if (!(WindowsControl is TreeView tv) || false == Alive(tv)) return null;
3546 TreeNode node = null;
3547 OnUi(tv, () => node = tv.Nodes.Add(text));
3548 return node != null ? new GPALTreeNode(node) : null;
3549 }
3550 }
3551
3556 public class GPALTreeNode
3557 {
3558 private readonly TreeNode _node;
3559
3560 internal GPALTreeNode(TreeNode node) { _node = node; }
3561
3562 // the node this handle stands for, so the tree can hand one back and be given one
3563 internal TreeNode Node => _node;
3564
3568 public string Text
3569 {
3570 get
3571 {
3572 TreeView tv = _node.TreeView;
3573 string retVal = null;
3574
3575 GPALControl.OnUiOrHere(tv, () => retVal = _node.Text);
3576
3577 return retVal;
3578 }
3579 set
3580 {
3581 TreeView tv = _node.TreeView;
3582
3583 GPALControl.OnUiOrHere(tv, () => _node.Text = value);
3584 }
3585 }
3586
3591 public object Tag
3592 {
3593 get { return _node.Tag; }
3594 set { _node.Tag = value; }
3595 }
3596
3600 public void Select()
3601 {
3602 TreeView tv = _node.TreeView;
3603
3604 if (null == tv) return;
3605
3606 GPALControl.OnUiOrHere(tv, () => tv.SelectedNode = _node);
3607 }
3608
3612 public void Collapse()
3613 {
3614 TreeView tv = _node.TreeView;
3615
3616 GPALControl.OnUiOrHere(tv, () => _node.Collapse());
3617 }
3618
3624 public GPALTreeNode AddChild(string text)
3625 {
3626 TreeNode child = null;
3627 TreeView tv = _node.TreeView;
3628 GPALControl.OnUiOrHere(tv, () => child = _node.Nodes.Add(text));
3629 return child != null ? new GPALTreeNode(child) : null;
3630 }
3631
3637 public bool Enabled
3638 {
3639 get
3640 {
3641 return SystemColors.GrayText != _node.ForeColor;
3642 }
3643 set
3644 {
3645 TreeView tv = _node.TreeView;
3646
3647 // the colour is the state. a TreeNode has no Enabled of its own, and keeping the answer on the
3648 // node rather than in a list beside it means it cannot drift from what is on screen
3649 MethodInvoker paint = () => _node.ForeColor = true == value ? SystemColors.WindowText : SystemColors.GrayText;
3650
3651 GPALControl.OnUiOrHere(tv, paint);
3652 }
3653 }
3654
3658 public void Expand()
3659 {
3660 TreeView tv = _node.TreeView;
3661 GPALControl.OnUiOrHere(tv, () => _node.Expand());
3662 }
3663 }
3664
3669 public class FileSelectorEventArgs : EventArgs
3670 {
3674 public bool Cancelled { get; }
3678 public string SelectedPath { get; }
3682 public IReadOnlyList<string> SelectedPaths { get; }
3683
3690 public FileSelectorEventArgs(bool cancelled, string selectedPath, string[] selectedPaths)
3691 {
3692 Cancelled = cancelled;
3693 SelectedPath = selectedPath ?? "";
3694 if (selectedPaths == null || selectedPaths.Length == 0)
3695 {
3696 SelectedPaths = Array.AsReadOnly(Array.Empty<string>());
3697 }
3698 else
3699 {
3700 SelectedPaths = Array.AsReadOnly(selectedPaths);
3701 }
3702 }
3703 }
3704
3719 public class GPALFileSelector : GPALControl, IAllowFileSelectorControlSettings
3720 {
3721 internal string _title = "Select File/Folder";
3722 internal string _initialDirectory = "";
3723 internal string _filter = "All files (*.*)|*.*";
3724 internal bool _selectFolder = false; // true = FolderBrowserDialog, false = OpenFileDialog
3725 internal bool _multiSelect = false;
3726 internal bool _compact = false;
3727 internal string _placeholder = "";
3728 internal string _selectedPath = "";
3729 internal readonly List<string> _selectedPaths = new List<string>();
3730
3731 internal GPALFileSelector()
3732 {
3733 ControlType = FormControlType.FileSelector;
3734 }
3735
3742 {
3743 _title = string.IsNullOrWhiteSpace(title) ? "Select File/Folder" : title;
3744 return this;
3745 }
3746
3753 {
3754 _initialDirectory = path;
3755 return this;
3756 }
3757
3765 {
3766 _filter = string.IsNullOrWhiteSpace(filter) ? "All files (*.*)|*.*" : filter;
3767 return this;
3768 }
3769
3777 {
3778 _selectFolder = folderMode;
3779 return this;
3780 }
3781
3789 {
3790 _multiSelect = allowMulti;
3791 return this;
3792 }
3793
3801 {
3802 _compact = compact;
3803 return this;
3804 }
3805
3809 {
3810 _placeholder = placeholder ?? "";
3811 return this;
3812 }
3813
3817 public string SelectedPath => _selectedPath;
3821 public IReadOnlyList<string> SelectedPaths => _selectedPaths.AsReadOnly();
3822
3828 internal void UpdateSelection(string[] paths)
3829 {
3830 _selectedPaths.Clear();
3831 if (paths != null && paths.Length > 0)
3832 {
3833 _selectedPaths.AddRange(paths);
3834 _selectedPath = paths[0];
3835 SyncDisplay();
3836 }
3837 else
3838 {
3839 _selectedPath = "";
3840 }
3841 }
3850 internal void NotifySelection(bool cancelled, string[] paths)
3851 {
3852 UpdateSelection(paths);
3853
3854 var args = new FileSelectorEventArgs(cancelled, SelectedPath, paths);
3855
3856 foreach (var (evt, handler) in Callbacks)
3857 {
3858 ControlEventType target = evt == ControlEventType.Default ? ControlEventType.Change : evt;
3859
3860 if (target == ControlEventType.Change ||
3861 target == ControlEventType.Click ||
3862 target == ControlEventType.Default)
3863 {
3864 ((EventHandler)handler)?.Invoke(this, args);
3865 }
3866 }
3867 }
3868 public void Clear()
3869 {
3870 _selectedPath = "";
3871 _selectedPaths.Clear();
3872 if (WindowsControl is Control ctrl && true == Alive(ctrl))
3873 {
3874 OnUi(ctrl, () => ctrl.Text = "");
3875 }
3876 }
3877
3878 public void SetPath(string path)
3879 {
3880 _selectedPath = path ?? "";
3881 _selectedPaths.Clear();
3882 if (!string.IsNullOrEmpty(_selectedPath))
3883 _selectedPaths.Add(_selectedPath);
3884 if (WindowsControl is Control ctrl && true == Alive(ctrl))
3885 {
3886 OnUi(ctrl, () => ctrl.Text = _selectedPath);
3887 }
3888 }
3889
3890 private void SyncDisplay()
3891 {
3892 if (WindowsControl is Control ctrl && true == Alive(ctrl))
3893 {
3894 string display = _selectedPaths.Count > 0
3895 ? string.Join("; ", _selectedPaths)
3896 : "";
3897
3898 OnUi(ctrl, () => ctrl.Text = display);
3899 }
3900 }
3901 }
3902
3903 #endregion <GPAL Controls>
3904}
3905
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
List< string > Filenames
Get the list of filenames.
Definition GPALFile.cs:536
Event arguments passed to a GPALFileSelector callback when the file/folder selection dialog is closed...
bool Cancelled
true if the user dismissed the dialog without selecting a path.
FileSelectorEventArgs(bool cancelled, string selectedPath, string[] selectedPaths)
Creates the event arguments for a completed file selector dialog.
string SelectedPath
The first selected path, or an empty string if none was selected.
IReadOnlyList< string > SelectedPaths
All selected paths. Contains more than one entry only when the dialog allowed multi-select.
A single item shared by GPAL menu bars and toolbars. Because it derives from GPALControl it speaks th...
bool Enabled
Whether the item can be clicked. Readable and settable while the form is up, so a menu can follow wha...
IAllowBarItemSettings WithImage(GPALFile file)
Sets the item's icon by loading it from the file referenced by a GPALFile.
new IAllowBarItemSettings WithText(string text)
Sets the item's display text.
IAllowBarItemSettings WithImage(System.Drawing.Image image)
Sets the item's icon from an in-memory image.
IAllowBarItemSettings WithEnabled(bool trueFalse=true)
Greys the item and stops it being clicked. A menu item that does not apply is worth showing greyed ra...
bool Enabled
Runtime access to Enable/Disable the button.
IAllowControlSettingsEnabledAndDefault Default
Fluent interface to designate this button as the default button. NOTE: Do NOT define more than one d...
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
SeriesCollection Series
Underlying Windows chart Series collection.
TitleCollection Titles
Underlying Windows chart Titles collection.
IAllowControlSettingsAndChartSettings WithPalette(ChartColorPalette colorPalette)
Set the color palette of the chart bars.
IAllowControlSettingsAndChartSettings WithSeries(Series series)
Sets the series to plot in the chart.
IAllowControlSettingsAndChartSettings WithTitle(string title)
The title to display at the top of the chart.
LegendCollection Legends
Underlying Windows chart Legends collection.
IAllowControlSettings WithChecked(bool isChecked)
Sets whether the checkbox is checked.
bool Enabled
Runtime access to Enable/Disable the checkbox.
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
bool Checked
Runtime access to un/check the checkbox. Setting this writes through to the live control (if realized...
IAllowComboBoxControlSettings WithSelectedIndex(int index)
Sets which item is initially selected, by index. Ignored if index is out of range of the items added...
IAllowComboBoxControlSettings WithItems(Array itemArray)
Appends the items from a non-generic array (e.g. the result of Enum.GetValues) to the dropdown list,...
void ClearItems()
Empties the dropdown, so its contents can be replaced with a list that suits what has been chosen els...
bool IsItemEnabled(string item)
Whether the item is currently choosable.
IAllowComboBoxControlSettings WithSelectedItem(string name)
Sets which item is initially selected, by its display text. If the item is not found in the list the ...
void EnableItem(string item)
Makes a disabled item choosable again.
bool Enabled
The 0-based index of the currently selected item, or -1 if none is selected. Setting this writes thro...
void DisableItem(string item)
Greys an item and stops it being chosen, leaving it on the list. Use it for an option that does not a...
IAllowComboBoxControlSettings WithItems(params string[] itemArray)
Appends the given items to the dropdown list, in order.
string SelectedValue
The text of the currently selected item.
IAllowComboBoxControlSettings WithItems(IEnumerable< string > itemCollection)
Appends the given items to the dropdown list, in order.
Base class for all GPAL form controls.
IAllowControlSettings WithToolTipDelay(int delayInMs)
Sets how long the tooltip set via WithToolTip(string) remains visible.
IAllowToolTipDelay WithToolTip(string toolTipText)
Sets the tooltip text shown when hovering over the control.
int Height
The height of the control, in pixels. 0 [default] lets the control size itself.
ContentAlignment TextAlignment
The alignment currently in use.
string Text
For text based controls, get or set the text in the control. Setting this writes through to the live...
IAllowForEventControlSettings WithCallbackOffUIThread< TDelegate >(TDelegate handler)
Attaches a handler that runs off the UI thread, so the form keeps painting while it works....
IAllowControlSettings WithAlignment(ContentAlignment alignment)
Reserved for future use. Not yet implemented.
string Name
Control name, only used for Information and Exception channel messages. NOTE: Use ....
Font FontType
The font currently being used.
bool AutoSize
Autosize setting currently in use.
ContentAlignment ControlAlignment
The alignment of the control itself within its container.
dynamic ToGPALObject()
Return a typeof(GPALControl) so that controls do not have to be cast.
FormControlType ControlType
The control type of WindowsControl. Set when the control is instantiated with GPAL.
object Tag
for attaching value-added objects to carry along on form controls
IAllowControlSettings WithHeight(int height)
Sets the height of the control, in pixels.
IAllowControlSettingsAndFontSettings WithFont(Font font)
The font style and size for the label display.
IAllowControlSettings WithName(string name)
Sets the name for the control. Used in Information and Exception channel messages.
IAllowForEventControlSettings WithCallback< TDelegate >(TDelegate handler)
Sets an eventhandler to attach to the Windows form control event. See the GPAL controls for which ev...
IAllowControlSettingsAndFontSettings WithText(string text)
Set the text of the control.
dynamic WindowsControl
Underlying Windows form control, Button, Checkbox, Textbox, etc. NOTE: Use GPAL to instantiate form ...
IAllowControlSettings ForEvent(ControlEventType eventType)
Specifies which event the most recently added WithCallback<TDelegate>(TDelegate) handler should be at...
IAllowControlSettings WithShortcutKey(System.Windows.Forms.Keys shortCutKey)
Set the keyboard shortcut that will activate this controls eventhandler defined with ....
void OnUiThread(Action action)
Runs action on the UI thread. If the calling thread already owns the UI, the action runs inline; oth...
IAllowControlSettingsAndFontSettings WithTextAlignment(ContentAlignment textAlignment)
The alignment of the text inside the label. .
IAllowControlSettingsAndFontSettings WithAutoSize(bool autoSize)
Whether or not the label contents autosize to fit the label. .
IAllowControlSettings WithTag(object tag)
Set the text of the control.
IAllowDataGridViewControlSettings WithReadOnly(bool readOnly)
Sets whether the grid's cells are read-only.
IAllowDataGridViewControlSettings WithColumns(params string[] columnNames)
Sets the column header names for the grid. If not specified and WithAutoGenerateColumns(bool) is true...
IAllowDataGridViewControlSettings WithGrid(IGPALGrid< string > grid)
Sets the data grid to display and immediately syncs it to the underlying Windows control via SyncGrid...
IReadOnlyList< string > ColumnNames
The column header names set via WithColumns(string[]).
IAllowDataGridViewControlSettings WithAutoGenerateColumns(bool autoGenerate)
Sets whether columns should be auto-generated (named "Column 1", "Column 2", etc.) when no column nam...
IGPALGrid< string > CurrentGrid
The grid data currently displayed, as set via WithGrid(IGPALGrid<string>). Setting this refreshes the...
bool AutoGenerateColumns
Whether columns are auto-generated when no column names were provided, as set via WithAutoGenerateCol...
IAllowDateTimePickerControlSettings WithValue(DateTime value)
Sets the initial date/time value. Defaults to the current date/time.
string CustomFormat
The custom display format string, as set via WithCustomFormat(string).
IAllowDateTimePickerControlSettings WithCustomFormat(string formatString)
Sets a custom display format string, used when Format is DateTimePickerFormat.Custom.
IAllowDateTimePickerControlSettings WithFormat(DateTimePickerFormat format)
Sets the display format. Defaults to DateTimePickerFormat.Long. Use DateTimePickerFormat....
DateTime Value
Runtime access to the date/time. Reads the live control once the form is up, so the date the user pic...
DateTimePickerFormat Format
The display format, as set via WithFormat(DateTimePickerFormat).
IAllowFileSelectorControlSettings WithInitialDirectory(string path)
Sets the directory the selection dialog initially opens to.
IAllowFileSelectorControlSettings WithPlaceholder(string placeholder)
Sets placeholder hint text shown in the text field when it is empty. Only applies in compact mode.
IAllowFileSelectorControlSettings WithFilter(string filter)
Sets the file type filter used by the OpenFileDialog when WithSelectFolder(bool) is false.
IAllowFileSelectorControlSettings WithMultiSelect(bool allowMulti)
Sets whether the file dialog allows selecting multiple files. Has no effect when WithSelectFolder(boo...
IAllowFileSelectorControlSettings WithCompact(bool compact)
When true, renders as an editable text field with a small "..." browse button on the right,...
IAllowFileSelectorControlSettings WithSelectFolder(bool folderMode)
Sets whether the control opens a FolderBrowserDialog (folder selection) or an OpenFileDialog (file se...
IAllowFileSelectorControlSettings WithTitle(string title)
Sets the title bar text of the selection dialog.
IReadOnlyList< string > SelectedPaths
All paths selected in the most recent dialog.
string SelectedPath
The first path selected in the most recent dialog, or an empty string if none has been selected.
IAllowGroupBoxControlSettings WithFormControl(object gPALFormControl)
Add a control inside the box. A container can go in here too, so a group box can hold a splitter.
new GPALGroupBox ToGPALObject()
Returns this group box so it can be handed to a form or to another container.
new IAllowGroupBoxControlSettings WithText(string text)
The caption along the top of the box.
bool ShowPassword
The input a password or other sensitive information. The input will be masked with a bullet characte...
bool Enabled
Runtime access to Enable/Disable the input.
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
bool Sortable
Whether column-click sorting is enabled.
void EnableItem(int index)
Makes a disabled row selectable again.
IAllowListVIewControlSettings WithColumnWeight(double weight)
IAllowListVIewControlSettings WithSortable(bool sortable=true)
Enables click-to-sort on all columns. Clicking a column header sorts ascending; clicking again revers...
IReadOnlyList< string > Columns
The column header names set via WithColumns(string[]).
IAllowListVIewControlSettings WithFullRowSelect(bool enable)
Sets whether selecting an item highlights the entire row. Defaults to true.
string SelectedText
The text of the first selected item, or null if nothing is selected.
bool IsItemEnabled(int index)
Whether the row at this position can be selected.
void DisableItem(int index)
Greys a row and stops it being selected, leaving it on the list. Rows are addressed by position,...
IAllowListVIewControlSettings WithCheckBoxes(bool enable=true)
Sets the column headers for the list view (used in ListViewMode.Details mode).
IReadOnlyList< double > ColumnWeights
Proportional column weights set via WithColumnWeight(double).
View ViewMode
The view mode set via WithView(ListViewMode), mapped to the underlying WinForms View.
bool CheckBoxes
Whether rows carry a checkbox. Settable while the form is up.
IAllowListVIewControlSettings WithView(ListViewMode view)
Sets how items are displayed (Details, List, LargeIcon, SmallIcon, or Tile). Defaults to ListViewMode...
bool MultiSelect
Whether multi-select is enabled, as set via WithMultiSelect(bool).
bool FullRowSelect
Whether full-row selection is enabled, as set via WithFullRowSelect(bool).
IAllowListVIewControlSettings WithMultiSelect(bool enable)
Sets whether multiple items can be selected at once. Defaults to false.
IAllowMenuBarControlSettings WithMenu(string topMenu)
Opens (or reselects) a top-level menu. Subsequent WithItem(GPALBarItem) and WithSeparator calls attac...
IAllowMenuBarControlSettings WithItem(GPALBarItem item)
Adds an item to the currently open top-level menu.
IAllowMenuBarControlSettings WithSeparator()
Adds a separator line to the currently open top-level menu.
decimal Minimum
The minimum allowed value, as set via WithMinimum(decimal).
int DecimalPlaces
The number of decimal places displayed, as set via WithDecimalPlaces(int).
IAllowNumericUpDownControlSettings WithIncrement(decimal inc)
Sets the amount the value changes when the up/down spinner is clicked. Defaults to 1.
IAllowNumericUpDownControlSettings WithMinimum(decimal min)
Sets the minimum allowed value. Defaults to 0.
decimal Maximum
The maximum allowed value, as set via WithMaximum(decimal).
IAllowNumericUpDownControlSettings WithDecimalPlaces(int places)
Sets how many decimal places are displayed. Defaults to 0.
decimal Increment
The spinner increment, as set via WithIncrement(decimal).
IAllowNumericUpDownControlSettings WithValue(decimal value)
Sets the initial value.
IAllowNumericUpDownControlSettings WithMaximum(decimal max)
Sets the maximum allowed value. Defaults to 100.
decimal Value
Runtime access to the value. Reads the live control once the form is up, so a value the user spun to ...
IAllowProgressBarControlSettings WithValue(int value)
Sets the initial progress value, clamped to Minimum/Maximum, and syncs it to the underlying control.
int Maximum
The maximum progress value, as set via WithMaximum(int).
ProgressBarStyle Style
The current visual style. Setting this writes through to the live control (if realized),...
int Value
The current progress value, clamped to Minimum/Maximum. Setting this writes through to the live contr...
int Minimum
The minimum progress value, as set via WithMinimum(int).
IAllowProgressBarControlSettings WithStyle(ProgressBarStyle style)
Sets the visual style of the progress bar (e.g. continuous, blocks, marquee).
IAllowProgressBarControlSettings WithMinimum(int min)
Sets the minimum progress value. If Value is below this, it is raised to match.
void Increment(int step=1)
Runtime access to advance the progress value by step , clamped to Minimum/Maximum.
IAllowProgressBarControlSettings WithMaximum(int max)
Sets the maximum progress value. If Value exceeds this, it is lowered to match.
bool Enabled
Runtime access to Enable/Disable the radiobutton.
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
bool Checked
Runtime access to un/check the radiobutton. Setting this writes through to the live control (if reali...
IAllowControlSettings WithChecked(bool isChecked)
Sets whether the radiobutton is checked.
new string Text
The Markdown source text. Setting this re-renders headings, bold spans, and bullet/numbered lists (wi...
IAllowControlSettings WithEnabled(bool enabled=true)
Fluent interface to set the initial enabled state of the control.
new IAllowRichTextBoxControlSettings WithText(string text)
Sets the initial Markdown source text.
bool Enabled
Runtime access to Enable/Disable the rich text box.
IAllowSplitterControlSettings VSplit(int topHeight)
A horizontal bar with a top and a bottom pane, the top one this many pixels tall.
IAllowSplitterControlSettings VSplit(double topPercent)
A horizontal bar with a top and a bottom pane, the top one this share of the height,...
new IAllowSplitterControlSettings WithHeight(int height)
A vertical bar with a left and a right pane, the left one this many pixels wide.
new GPALSplitter ToGPALObject()
Returns this splitter so it can be handed to a form or to another container.
IAllowSplitterControlSettings HSplit(double leftPercent)
A vertical bar with a left and a right pane, the left one this share of the width,...
IAllowSplitterControlSettings WithFormControl(object gPALFormControl)
Add a control to the splitter. Everything before GPAL.SplitRight or GPAL.SplitBottom belongs to the f...
new IAllowStatusStripControlSettings WithText(string text)
Sets the initial status text. Blank or whitespace falls back to "Ready".
new string Text
The status text. Blank or whitespace falls back to "Ready". Setting this writes through to the first ...
void Activate()
Activate (switch to) this tab at runtime, bringing its controls into view.
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
bool Enabled
Runtime access to Enable/Disable the tab.
TableLayoutPanelGrowStyle GrowStyle
The direction in which the panel automatically adds new rows or columns when it runs out of space,...
int RowCount
The number of rows in the panel, mirroring the underlying TableLayoutPanel.RowCount.
bool Enabled
Runtime access to Enable/Disable the table layout panel.
int ColumnCount
The number of columns in the panel, mirroring the underlying TableLayoutPanel.ColumnCount.
IAllowControlSettingsAndTableLayoutPanelSettings WithColumnCount(int columnCount)
Sets the number of columns in the panel.
IAllowControlSettingsAndTableLayoutPanelSettings WithRowCount(int rowCount)
Sets the number of rows in the panel.
IAllowControlSettingsAndTableLayoutPanelSettings WithGrowStyle(TableLayoutPanelGrowStyle tableLayoutPanelGrowStyle)
Sets the direction in which the panel automatically adds new rows or columns when it runs out of spac...
IAllowControlSettingsAndTableLayoutPanelSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
IAllowControlSettings WithEnabled(bool enabled)
Fluent interface to set the initial state of the control.
bool Enabled
Runtime access to Enable/Disable the textarea.
void Clear()
Empties the text area, thread-safe. The same rules as AppendLine(string, int): nothing to clear is a ...
void AppendLine(string text, int maxLines=0)
Appends text as a new line, thread-safe. When maxLines is greater than zero and the control already...
void ScrollToEnd()
Scrolls the text area to the last line, thread-safe.
IAllowToolbarControlSettings WithItem(GPALBarItem item)
Adds a button to the toolbar.
IAllowToolbarControlSettings WithSeparator()
Adds a vertical separator between toolbar buttons.
A handle to a tree node returned by GPALTreeView.AddNode(string). Use it to add children and control ...
object Tag
Anything the workflow wants to carry on the node: the selector it stands for, the record it came from...
void Collapse()
Hides this node's children, thread-safe.
bool Enabled
Whether this node can be selected. A disabled node stays on the tree, greyed, because a branch that v...
void Select()
Makes this the selected node, thread-safe.
GPALTreeNode AddChild(string text)
Adds a child node under this node, thread-safe.
void Expand()
Expands this node to show its children, thread-safe.
bool ShowLines
Whether lines are drawn between sibling nodes and their parent, as set via WithShowLines(bool).
IAllowTreeViewControlSettings WithShowLines(bool show)
Sets whether lines are drawn between sibling nodes and their parent.
IAllowTreeViewControlSettings WithCheckBoxes(bool enable)
Sets whether checkboxes are displayed next to each tree node.
void BeginUpdate()
Stops the tree redrawing until EndUpdate is called. Building a large tree without it is what makes on...
GPALTreeNode SelectedNode
The node currently selected, or null when nothing is. Settable, so a workflow can put the user where ...
IAllowTreeViewControlSettings WithShowPlusMinus(bool show)
Sets whether plus/minus expand buttons are displayed next to nodes that have children.
void ClearNodes()
Removes all nodes from the tree, thread-safe.
void EndUpdate()
Lets the tree redraw again after BeginUpdate.
bool ShowPlusMinus
Whether plus/minus expand buttons are displayed, as set via WithShowPlusMinus(bool).
GPALTreeNode AddNode(string text)
Adds a top-level node and returns a GPALTreeNode for attaching children. Thread-safe.
bool CheckBoxes
Whether checkboxes are displayed next to each tree node, as set via WithCheckBoxes(bool).
https://stackoverflow.com/questions/11873378/adding-placeholder-text-to-textbox
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406