18using System.Collections.Concurrent;
19using System.Collections.Generic;
23using System.Text.RegularExpressions;
24using System.Threading.Tasks;
26using System.Windows.Forms;
27using System.Windows.Forms.DataVisualization.Charting;
38 #region <GPAL Controls>
48 public Font
FontType {
get;
internal set; } =
new Font(
"Georgia", 12);
53 public ContentAlignment
TextAlignment {
get;
internal set; } = ContentAlignment.MiddleLeft;
58 public bool AutoSize {
get;
internal set; } =
true;
111 public int Height {
get;
internal set; } = 0;
116 public int Width {
get;
internal set; } = 0;
117 private string _text;
138 PostToUi(ctrl, () => ctrl.Text = value);
146 public string Name {
get;
internal set; }
150 public object Tag {
get;
internal set; }
157 get => _windowsControl;
160 _windowsControl = value;
169 private dynamic _windowsControl;
175 internal virtual void Realized()
180 while (
true == pendingLines.TryDequeue(out
string _))
189 private readonly ConcurrentQueue<string> pendingLines =
new ConcurrentQueue<string>();
190 private readonly
object appendGate =
new object();
191 private bool waitingForHandle =
false;
193 internal void AppendLineCore(
string text,
int maxLines)
197 pendingLines.Enqueue(text);
199 DrainLines(maxLines);
203 private void DrainLines(
int maxLines)
207 if (!(
WindowsControl is TextBoxBase tb) ||
false == Alive(tb))
return;
211 if (
false == tb.IsHandleCreated)
215 if (
false == waitingForHandle)
217 waitingForHandle =
true;
218 tb.HandleCreated += (sender, args) => DrainLines(maxLines);
225 MethodInvoker drain = () =>
228 if (
false == Alive(tb) ||
false == tb.IsHandleCreated)
return;
233 List<string> arrived =
new List<string>();
235 while (
true == pendingLines.TryDequeue(out
string line))
238 if (0 == arrived.Count)
return;
240 string[] held = 0 < maxLines ? tb.Lines :
new string[0];
242 if (0 < maxLines && held.Length + arrived.Count > maxLines)
245 List<string> keeping =
new List<string>(maxLines);
247 int roomForHeld = maxLines - Math.Min(arrived.Count, maxLines);
249 for (
int each = Math.Max(0, held.Length - roomForHeld); each < held.Length; each++)
250 keeping.Add(held[each]);
252 for (
int each = Math.Max(0, arrived.Count - maxLines); each < arrived.Count; each++)
253 keeping.Add(arrived[each]);
255 tb.Text =
string.Join(
"\r\n", keeping);
259 if (0 < tb.TextLength) tb.AppendText(
"\r\n");
261 tb.AppendText(
string.Join(
"\r\n", arrived));
264 tb.SelectionStart = tb.TextLength;
272 internal void ClearLinesCore()
275 while (
true == pendingLines.TryDequeue(out
string dropped)) { }
277 if (!(
WindowsControl is TextBoxBase tb) ||
false == Alive(tb))
return;
279 if (
false == tb.IsHandleCreated)
281 tb.Text =
string.Empty;
285 MethodInvoker clear = () =>
287 if (
false == Alive(tb) ||
false == tb.IsHandleCreated)
return;
295 internal void ScrollToEndCore()
297 if (!(
WindowsControl is TextBoxBase tb) ||
false == Alive(tb))
return;
299 PostToUi(tb, () => tb.ScrollToCaret());
312 internal static bool Alive(Control control)
318 return null != control
319 &&
false == control.IsDisposed
320 &&
false == control.Disposing;
332 internal bool ReadOnly {
get;
set; }
333 internal bool IsChecked {
get;
set; }
334 internal bool IsEnabled {
get;
set; } =
true;
348 bool retVal = IsEnabled;
351 retVal = OnUiRead(control, () => control.Enabled, IsEnabled);
358 OnUi(control, () => control.Enabled = value);
385 IsReadOnly = readOnly;
392 internal bool IsReadOnly {
get;
set; }
393 internal string ToolTipText {
get;
private set; }
394 internal int ToolTipAutoPopDelayMs {
get;
private set; } = 5000;
395 internal System.Windows.Forms.Keys ShortCutKey {
get;
set; }
396 internal List<(ControlEventType EventType, Delegate Handler)> Callbacks {
get; } =
new List<(ControlEventType EventType, Delegate Handler)>();
415 where TDelegate : Delegate
417 Callbacks.Add((ControlEventType.Default, WrapHandler(handler, RunOnUiThread) ?? (Delegate)handler));
427 private const double UiHeldWarningSeconds = 2.0;
433 private int _callbackDepth;
440 private int _callbackPending;
446 private const int CallbackBacklogWarning = 5;
452 private System.Threading.Tasks.Task _callbackChain = System.Threading.Tasks.Task.CompletedTask;
458 private readonly
object _callbackChainGate =
new object();
464 private string CallbackOwner =>
true ==
string.IsNullOrEmpty(
Name) ? GetType().Name :
Name;
471 private void EnterCallback()
473 if (1 < System.Threading.Interlocked.Increment(ref _callbackDepth))
474 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);
482 private void RunOnUiThread(Action call)
484 System.Diagnostics.Stopwatch held = System.Diagnostics.Stopwatch.StartNew();
495 System.Threading.Interlocked.Decrement(ref _callbackDepth);
498 if (UiHeldWarningSeconds < held.Elapsed.TotalSeconds)
499 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);
536 where TDelegate : Delegate
541 bool answersAsItReturns = handler is DragEventHandler || handler is KeyEventHandler;
543 Delegate wrapped =
true == answersAsItReturns ? null : WrapHandler(handler, QueueInBackground);
548 ? $
"The event reads its answer from [{typeof(TDelegate).Name}] as soon as the handler returns, so the handler for [{CallbackOwner}] stays on the UI thread"
549 : $
"[{typeof(TDelegate).Name}] is not a handler type GPAL knows, so the handler for [{CallbackOwner}] stays on the UI thread",
this, GPALObjectType.Other);
551 wrapped = WrapHandler(handler, RunOnUiThread) ?? (Delegate)handler;
554 Callbacks.Add((ControlEventType.Default, wrapped));
564 private void RunInBackground(Action call)
576 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"A background handler for [{CallbackOwner}] ended in an exception",
this, GPALObjectType.Other, ex);
580 System.Threading.Interlocked.Decrement(ref _callbackDepth);
581 System.Threading.Interlocked.Decrement(ref _callbackPending);
594 private Delegate WrapHandler<TDelegate>(TDelegate handler, Action<Action> runner)
595 where TDelegate : Delegate
601 case EventHandler typed:
602 retVal = (EventHandler)((sender, args) => runner(() => typed(sender, args)));
605 case KeyEventHandler typed:
606 retVal = (KeyEventHandler)((sender, args) => runner(() => typed(sender, args)));
609 case DragEventHandler typed:
610 retVal = (DragEventHandler)((sender, args) => runner(() => typed(sender, args)));
613 case ScrollEventHandler typed:
614 retVal = (ScrollEventHandler)((sender, args) => runner(() => typed(sender, args)));
617 case SplitterEventHandler typed:
618 retVal = (SplitterEventHandler)((sender, args) => runner(() => typed(sender, args)));
621 case ItemCheckedEventHandler typed:
622 retVal = (ItemCheckedEventHandler)((sender, args) => runner(() => typed(sender, args)));
625 case TreeViewEventHandler typed:
626 retVal = (TreeViewEventHandler)((sender, args) => runner(() => typed(sender, args)));
629 case DataGridViewCellEventHandler typed:
630 retVal = (DataGridViewCellEventHandler)((sender, args) => runner(() => typed(sender, args)));
646 private void QueueInBackground(Action call)
648 if (CallbackBacklogWarning == System.Threading.Interlocked.Increment(ref _callbackPending))
649 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);
651 lock (_callbackChainGate)
652 _callbackChain = _callbackChain.ContinueWith(previous => RunInBackground(call));
666 if (Callbacks.Count > 0)
668 var last = Callbacks[Callbacks.Count - 1];
669 if (last.EventType == ControlEventType.Default)
671 Callbacks[Callbacks.Count - 1] = (eventType, last.Handler);
677 Callbacks.Add((eventType,
null));
687 ToolTipText = toolTipText ??
"";
698 ToolTipAutoPopDelayMs = Math.Max(1000, delayInMs);
718 ShortCutKey = shortCutKey;
731 internal static void OnUi(Control control, MethodInvoker work)
733 if (
false == Alive(control))
740 if (
true == control.IsHandleCreated &&
true == control.InvokeRequired)
741 control.Invoke(work);
745 catch (ObjectDisposedException)
749 catch (InvalidOperationException)
763 internal static void OnUiOrHere(Control control, MethodInvoker work)
765 if (
true == Alive(control))
778 internal static void PostToUi(Control control, MethodInvoker work)
780 if (
false == Alive(control))
785 if (
true == control.IsHandleCreated &&
true == control.InvokeRequired)
786 control.BeginInvoke(work);
790 catch (ObjectDisposedException)
794 catch (InvalidOperationException)
810 internal static T OnUiRead<T>(Control control, Func<T> read, T whenGone)
814 if (
true == Alive(control))
817 retVal =
true == control.IsHandleCreated &&
true == control.InvokeRequired
818 ? (T)control.Invoke(read)
821 catch (ObjectDisposedException)
825 catch (InvalidOperationException)
883 OnUi(control, () => control.Tag = tag);
896 this.Height = height;
997 internal ChartColorPalette ChartColorPalette {
get;
set; }
998 internal Series mySeries {
get;
set; }
999 internal string Title {
get;
set; }
1001 internal GPALChart()
1004 Chart chart =
new Chart
1008 Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
1009 Dock = DockStyle.Fill,
1011 chart.ChartAreas.Add(
new ChartArea(
Name));
1078 internal ChartAreaCollection ChartAreas
1124 internal bool IsDefault {
get;
set; }
1125 internal GPALButton()
1164 internal GPALCheckbox()
1175 this.Checked = isChecked;
1187 bool retVal = IsChecked;
1190 retVal = OnUiRead(cb, () => cb.Checked, retVal);
1198 OnUi(cb, () => cb.Checked = value);
1226 private IGPALGrid<string> _grid;
1227 private readonly List<string> _columnNames =
new List<string>();
1228 private bool _autoGenerateColumns =
false;
1230 internal GPALDataGridView()
1243 if (columnNames !=
null)
1245 _columnNames.AddRange(columnNames);
1257 base.ReadOnly = readOnly;
1269 _autoGenerateColumns = autoGenerate;
1281 this.CurrentGrid = grid;
1296 internal readonly List<int> _columnWidths =
new List<int>();
1313 _columnWidths.Clear();
1316 _columnWidths.AddRange(widths);
1318 ApplyColumnWidths();
1326 internal readonly List<bool> _columnWrap =
new List<bool>();
1349 _columnWrap.Clear();
1353 _columnWrap.AddRange(wrap);
1365 internal void ApplyWrap()
1367 if (!(
WindowsControl is DataGridView dgv) ||
false == Alive(dgv))
1370 if (
true == dgv.InvokeRequired)
1372 OnUi(dgv, ApplyWrap);
1378 bool everyColumn = 0 == _columnWrap.Count &&
true == wrapAsked;
1380 for (
int column = 0; column < dgv.Columns.Count; column++)
1382 bool wraps =
true == everyColumn
1383 || (column < _columnWrap.Count &&
true == _columnWrap[column]);
1385 dgv.Columns[column].DefaultCellStyle.WrapMode =
true == wraps
1386 ? DataGridViewTriState.True
1387 : DataGridViewTriState.False;
1390 dgv.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);
1394 private bool wrapAsked =
false;
1397 internal readonly List<double> _columnShares =
new List<double>();
1417 _columnShares.Clear();
1420 _columnShares.AddRange(shares);
1422 ApplyColumnWidths();
1429 internal void ApplyColumnWidths()
1431 if (!(
WindowsControl is DataGridView dgv) ||
false == Alive(dgv))
1436 if (0 < _columnShares.Count)
1438 dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
1440 for (
int column = 0; column < _columnShares.Count && column < dgv.Columns.Count; column++)
1441 dgv.Columns[column].FillWeight = (
float)(0 < _columnShares[column] ? _columnShares[column] : 1);
1444 if (0 == _columnWidths.Count)
1447 if (
true == dgv.InvokeRequired)
1449 OnUi(dgv, ApplyColumnWidths);
1453 for (
int column = 0; column < _columnWidths.Count && column < dgv.Columns.Count; column++)
1454 if (0 < _columnWidths[column])
1455 dgv.Columns[column].Width = _columnWidths[column];
1465 internal bool LastColumnFills => 0 < _columnWidths.Count && _columnWidths.Count == (
WindowsControl as DataGridView)?.Columns.Count && 0 == _columnShares.Count;
1468 private bool filling =
false;
1477 internal void FillLastColumn()
1479 if (!(
WindowsControl is DataGridView dgv) ||
false == Alive(dgv) ||
false == LastColumnFills)
1482 if (
true == dgv.InvokeRequired)
1484 OnUi(dgv, FillLastColumn);
1488 int last = dgv.Columns.Count - 1;
1490 if (0 > last ||
true == filling)
1499 dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
1501 if (last < _columnWidths.Count && 0 < _columnWidths[last])
1502 dgv.Columns[last].Width = _columnWidths[last];
1506 for (
int before = 0; before < last; before++)
1507 used += dgv.Columns[before].Width;
1512 int verticalBar = 0;
1514 foreach (Control child
in dgv.Controls)
1515 if (child is VScrollBar bar &&
true == bar.Visible)
1516 verticalBar = bar.Width;
1519 int left = dgv.ClientSize.Width - used - (
true == dgv.RowHeadersVisible ? dgv.RowHeadersWidth : 0) - verticalBar - GPALListView.FillMargin;
1521 if (left > dgv.Columns[last].Width)
1522 dgv.Columns[last].Width = left;
1541 IGPALGrid<string> rows = DatabaseHelper.DataTableToGrid(dataTable, out List<DatabaseSettings.DatabaseColumn> columns);
1545 if (0 == _columnNames.Count)
1546 foreach (DatabaseSettings.DatabaseColumn column in columns)
1547 _columnNames.Add(column.ColumnName);
1578 DataTable table =
null;
1580 if (
null != database)
1581 database.SaveTo(out table);
1597 IGPALGrid<string> rows =
GPAL.
Grid.ToGPALObject();
1601 FileHelper.GetGrid(((IGPALFileInternal)file).FileSettings, out List<IGPALGrid<string>> tokens);
1606 foreach (IGPALGrid<string> token
in tokens)
1607 foreach (List<string> row
in token)
1608 rows.AddRow(
new List<string>(row));
1616 private int _startRow = 0;
1617 private int _startColumn = 0;
1618 private int _endRow = 0;
1619 private int _endColumn = 0;
1639 _startColumn = column;
1661 _endColumn = column;
1679 IGPALGrid<string> reading =
GPAL.
Grid.ToGPALObject();
1683 if (
true == dgv.InvokeRequired)
1684 OnUi(dgv, () => ReadInto(dgv, reading));
1686 ReadInto(dgv, reading);
1688 else if (
null != _grid)
1689 foreach (List<string> row
in _grid)
1690 reading.AddRow(
new List<string>(row));
1702 private void ReadInto(DataGridView dgv, IGPALGrid<string> reading)
1704 int firstRow = 1 > _startRow ? 1 : _startRow;
1705 int lastRow = 1 > _endRow ? dgv.Rows.Count : (_endRow > dgv.Rows.Count ? dgv.Rows.Count : _endRow);
1706 int firstColumn = 1 > _startColumn ? 1 : _startColumn;
1707 int lastColumn = 1 > _endColumn ? dgv.Columns.Count : (_endColumn > dgv.Columns.Count ? dgv.Columns.Count : _endColumn);
1711 if (0 < dgv.Columns.Count)
1713 List<string> headers =
new List<string>();
1715 for (
int column = firstColumn; column <= lastColumn; column++)
1716 headers.Add(dgv.Columns[column - 1].HeaderText ??
string.Empty);
1718 reading.AddRow(headers);
1721 for (
int row = firstRow; row <= lastRow; row++)
1723 if (
true == dgv.Rows[row - 1].IsNewRow)
1726 List<string> cells =
new List<string>();
1728 for (
int column = firstColumn; column <= lastColumn; column++)
1729 cells.Add(dgv.Rows[row - 1].Cells[column - 1].Value?.ToString() ??
string.Empty);
1731 reading.AddRow(cells);
1735 internal void SyncGridToControl()
1737 if (!(
WindowsControl is DataGridView dgv) ||
false == Alive(dgv) || _grid ==
null)
1742 if (dgv.InvokeRequired)
1744 OnUi(dgv, SyncGridToControl);
1749 dgv.Columns.Clear();
1752 int columnCount = 0;
1753 foreach (List<string> row
in _grid)
1757 columnCount = row.Count;
1763 if (_columnNames.Count > 0)
1766 foreach (
string header
in _columnNames)
1768 if (colIndex >= columnCount)
break;
1769 dgv.Columns.Add(header, header);
1773 else if (_autoGenerateColumns && columnCount > 0)
1775 for (
int i = 0; i < columnCount; i++)
1777 string header = $
"Column {i + 1}";
1778 dgv.Columns.Add(header, header);
1783 foreach (List<string> rowData
in _grid)
1785 if (rowData ==
null)
continue;
1787 var dgvRow =
new DataGridViewRow();
1788 var cells =
new DataGridViewCell[rowData.Count];
1791 foreach (
string cellValue
in rowData)
1793 cells[colIndex] =
new DataGridViewTextBoxCell
1795 Value = cellValue ??
""
1800 dgvRow.Cells.AddRange(cells);
1801 dgv.Rows.Add(dgvRow);
1804 dgv.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
1805 dgv.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);
1811 ApplyColumnWidths();
1825 SyncGridToControl();
1831 public IReadOnlyList<string>
ColumnNames => _columnNames.AsReadOnly();
1857 internal bool InputIsPassword {
get;
set; } =
false;
1858 internal string PlaceHolder {
get;
set; }
1859 internal GPALInput()
1877 return false == InputIsPassword;
1881 InputIsPassword =
false == value;
1885 char passwordChar =
true == value ?
'\0' :
'●';
1887 OnUi(ptbInput, () => ptbInput.PasswordChar = passwordChar);
1896 InputIsPassword =
true;
1897 if (
true == String.IsNullOrEmpty(PlaceHolder))
1898 PlaceHolder =
"Enter Password";
1902 public IAllowControlSettingsPlaceholderAndPassword WithPlaceholder(
string PlaceholderText)
1904 PlaceHolder = PlaceholderText;
1905 if (
WindowsControl is PlaceholderTextBox tb &&
true == Alive(tb))
1906 tb.PlaceholderText = PlaceholderText;
1912 IsReadOnly = readOnly;
1914 tb.ReadOnly = readOnly;
1942 internal GPALLabel()
1961 internal GPALSpacer()
1988 internal GPALLinkLabel()
1994 public string Url {
get;
internal set; }
2012 internal void Open()
2014 string target =
false ==
string.IsNullOrWhiteSpace(
Url) ?
Url : Text;
2016 if (
true == target?.StartsWith(
"http://", StringComparison.OrdinalIgnoreCase)
2017 ||
true == target?.StartsWith(
"https://", StringComparison.OrdinalIgnoreCase))
2018 GPAL.Launcher.WithExecutable(target).Launch();
2049 internal GPALRadioButton()
2060 this.Checked = isChecked;
2072 bool retVal = IsChecked;
2075 retVal = OnUiRead(rb, () => rb.Checked, retVal);
2083 OnUi(rb, () => rb.Checked = value);
2117 public override bool Enabled
2123 bool retVal = IsEnabled;
2126 retVal = OnUiRead(page, () => page.Enabled, retVal);
2134 if (!(
WindowsControl is TabPage page) ||
false == Alive(page))
return;
2136 OnUi(page, () => page.Enabled = value);
2146 if (
WindowsControl is TabPage tabPage && tabPage.Parent is TabControl tabControl)
2147 OnUi(tabControl, () => tabControl.SelectedTab = tabPage);
2156 public class GPALSpliter
2158 internal GPALSpliter() { }
2182 internal bool IsHorizontalSplit {
get;
private set; } =
true;
2184 internal int Dimension {
get;
private set; }
2186 internal double Percentage {
get;
private set; }
2187 internal bool IsPercentage => 0 < Percentage;
2189 internal List<object> FormControls {
get; } =
new List<object>();
2191 internal GPALSplitter() { }
2210 IsHorizontalSplit =
true;
2211 Dimension = leftWidth;
2222 IsHorizontalSplit =
true;
2223 Percentage = leftPercent;
2233 IsHorizontalSplit =
false;
2234 Dimension = topHeight;
2245 IsHorizontalSplit =
false;
2246 Percentage = topPercent;
2257 FormControls.Add(gPALFormControl);
2283 internal List<object> FormControls {
get; } =
new List<object>();
2285 internal GPALGroupBox() { }
2304 FormControls.Add(gPALFormControl);
2323 internal GPALTableLayoutPanel()
2332 private TableLayoutPanelGrowStyle growStyle = TableLayoutPanelGrowStyle.AddRows;
2333 private int columnCount = 0;
2334 private int rowCount = 0;
2344 TableLayoutPanelGrowStyle retVal = growStyle;
2346 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2347 retVal = OnUiRead(panel, () => panel.GrowStyle, retVal);
2353 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2354 OnUi(panel, () => panel.GrowStyle = value);
2367 int retVal = columnCount;
2369 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2370 retVal = OnUiRead(panel, () => panel.ColumnCount, retVal);
2376 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2377 OnUi(panel, () => panel.ColumnCount = value);
2379 columnCount = value;
2391 int retVal = rowCount;
2393 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2394 retVal = OnUiRead(panel, () => panel.RowCount, retVal);
2400 if (
WindowsControl is TableLayoutPanel panel &&
true == Alive(panel))
2401 OnUi(panel, () => panel.RowCount = value);
2426 this.Enabled = enabled;
2437 this.GrowStyle = tableLayoutPanelGrowStyle;
2471 internal string PlaceHolder {
get;
set; }
2475 internal GPALTextArea()
2482 PlaceHolder = PlaceholderText;
2484 tb.PlaceholderText = PlaceholderText;
2490 IsReadOnly = readOnly;
2492 tb.ReadOnly = readOnly;
2502 public void AppendLine(
string text,
int maxLines = 0) => AppendLineCore(text, maxLines);
2507 public void Clear() => ClearLinesCore();
2531 private string _text =
string.Empty;
2533 internal GPALRichTextBox()
2550 _text = value ??
string.Empty;
2553 PostToUi(rtb, () => RenderMarkdown(rtb, _text));
2567 _text = 0 == _text.Length ? text : _text + Environment.NewLine + text;
2569 AppendLineCore(text, maxLines);
2577 _text =
string.Empty;
2601 IsReadOnly = readOnly;
2603 rtb.ReadOnly = readOnly;
2608 private static readonly Regex _inlinePattern =
new Regex(
2609 @"\*\*(?<b>.+?)\*\*|\*(?<i>.+?)\*|__(?<u>.+?)__|~~(?<s>.+?)~~|`(?<code>.+?)`|{(?<c>[^}]+)}(?<ct>.+?)\{\}",
2610 RegexOptions.Compiled);
2625 private static readonly Dictionary<string, Font> renderFonts =
new Dictionary<string, Font>();
2626 private static readonly
object renderFontGate =
new object();
2635 private static Font RenderFont(
string family,
float size, FontStyle style)
2637 string key = $
"{family}|{size}|{(int)style}";
2640 lock (renderFontGate)
2642 if (
false == renderFonts.TryGetValue(key, out retVal))
2644 retVal =
new Font(family, size, style);
2645 renderFonts[key] = retVal;
2652 internal static void RenderMarkdown(RichTextBox rtb,
string markdown)
2656 Font baseFont = rtb.Font;
2657 Color baseColor = rtb.ForeColor;
2658 string codeFamily =
"Courier New";
2659 string[] lines = (markdown ??
string.Empty).Replace(
"\r\n",
"\n").Split(
'\n');
2661 foreach (
string rawLine
in lines)
2663 string line = rawLine;
2665 int headingLevel = 0;
2666 while (headingLevel < line.Length && line[headingLevel] ==
'#')
2668 if (headingLevel > 0 && headingLevel <= 3 && headingLevel < line.Length && line[headingLevel] ==
' ')
2669 line = line.Substring(headingLevel + 1);
2673 int leadingSpaces = 0;
2674 while (leadingSpaces < line.Length && line[leadingSpaces] ==
' ')
2676 int indentLevel = leadingSpaces / 2;
2677 string trimmed = line.Substring(leadingSpaces);
2680 if (trimmed ==
"---")
2682 rtb.SelectionStart = rtb.TextLength;
2683 rtb.SelectionLength = 0;
2684 rtb.SelectionFont = RenderFont(baseFont.FontFamily.Name, baseFont.Size, FontStyle.Regular);
2685 rtb.SelectionColor = Color.Gray;
2686 rtb.AppendText(
new string(
'-', 48) +
"\n");
2690 bool isBulletItem =
false;
2691 bool isListItem =
false;
2692 if (trimmed.StartsWith(
"- ") || trimmed.StartsWith(
"* "))
2694 isBulletItem =
true;
2696 trimmed = trimmed.Substring(2);
2700 Match numberedMatch = Regex.Match(trimmed,
@"^(\d+\.\s+)(.*)$");
2701 if (numberedMatch.Success)
2704 trimmed = numberedMatch.Groups[1].Value + numberedMatch.Groups[2].Value;
2708 int lineStart = rtb.TextLength;
2710 FontStyle headingBase = headingLevel > 0 ? FontStyle.Bold : FontStyle.Regular;
2711 float headingSize = headingLevel > 0 ? baseFont.Size + (4 - headingLevel) * 2 : baseFont.Size;
2713 foreach ((
string segmentText, FontStyle style, Color? color,
bool isCode) in SplitInline(trimmed, headingBase))
2715 rtb.SelectionStart = rtb.TextLength;
2716 rtb.SelectionLength = 0;
2717 rtb.SelectionFont = isCode
2718 ? RenderFont(codeFamily, baseFont.Size, FontStyle.Regular)
2719 : RenderFont(baseFont.FontFamily.
Name, headingSize, style);
2720 rtb.SelectionColor = isCode ? Color.SteelBlue : (color ?? baseColor);
2721 rtb.AppendText(segmentText);
2723 rtb.AppendText(
"\n");
2727 rtb.SelectionStart = lineStart;
2728 rtb.SelectionLength = rtb.TextLength - lineStart;
2729 rtb.SelectionBullet = isBulletItem;
2730 rtb.SelectionIndent = 20 * (indentLevel + 1);
2734 rtb.SelectionStart = 0;
2735 rtb.SelectionLength = 0;
2744 private static List<(
string Text, FontStyle Style, Color? Color,
bool IsCode)> SplitInline(
string line, FontStyle inheritBase = FontStyle.Regular)
2746 var result =
new List<(string Text, FontStyle Style, Color? Color, bool IsCode)>();
2749 foreach (Match m
in _inlinePattern.Matches(line))
2752 result.Add((line.Substring(pos, m.Index - pos), inheritBase,
null,
false));
2754 if (m.Groups[
"b"].Success)
2755 result.Add((m.Groups[
"b"].Value, inheritBase | FontStyle.Bold,
null,
false));
2756 else if (m.Groups[
"i"].Success)
2757 result.Add((m.Groups[
"i"].Value, inheritBase | FontStyle.Italic,
null,
false));
2758 else if (m.Groups[
"u"].Success)
2759 result.Add((m.Groups[
"u"].Value, inheritBase | FontStyle.Underline,
null,
false));
2760 else if (m.Groups[
"s"].Success)
2761 result.Add((m.Groups[
"s"].Value, inheritBase | FontStyle.Strikeout,
null,
false));
2762 else if (m.Groups[
"code"].Success)
2763 result.Add((m.Groups[
"code"].Value, FontStyle.Regular,
null,
true));
2764 else if (m.Groups[
"c"].Success)
2766 Color named = Color.FromName(m.Groups[
"c"].Value);
2767 Color? resolved = named.IsKnownColor || named.A > 0 ? (Color?)named : null;
2769 foreach (var inner
in SplitInline(m.Groups[
"ct"].Value, inheritBase))
2770 result.Add((inner.Item1, inner.Item2, inner.Item3 ?? resolved, inner.Item4));
2773 pos = m.Index + m.Length;
2776 if (pos < line.Length)
2777 result.Add((line.Substring(pos), inheritBase,
null,
false));
2778 if (result.Count == 0)
2779 result.Add((
string.Empty, inheritBase,
null,
false));
2803 internal readonly List<string> items =
new List<string>();
2804 internal int selectedIndex = -1;
2805 internal string selectedItem =
null;
2809 internal readonly HashSet<string> disabledItems =
new HashSet<string>();
2812 internal int lastEnabledIndex = -1;
2814 internal GPALComboBox()
2826 if (
null != itemArray)
2828 items.AddRange(itemArray);
2829 ShowItems(itemArray);
2837 private void ShowItems(IEnumerable<string> itemsToShow)
2839 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2841 object[] adding = itemsToShow.Cast<
object>().ToArray();
2843 OnUi(cb, () => cb.Items.AddRange(adding));
2854 if (
true ==
string.IsNullOrEmpty(item))
return;
2856 disabledItems.Add(item);
2866 if (
true ==
string.IsNullOrEmpty(item))
return;
2868 disabledItems.Remove(item);
2879 return false == disabledItems.Contains(item);
2882 private void Redraw()
2884 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2886 OnUi(cb, () => cb.Invalidate());
2896 disabledItems.Clear();
2898 selectedItem =
null;
2899 lastEnabledIndex = -1;
2901 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2903 OnUi(cb, () => cb.Items.Clear());
2913 if (
null != itemCollection)
2915 items.AddRange(itemCollection);
2928 if (
null != itemArray)
2930 foreach (
object item
in itemArray)
2931 items.Add(item?.ToString() ??
string.Empty);
2944 selectedIndex = index;
2945 selectedItem =
null;
2957 selectedItem = name;
2971 if ((0 > idx) || (idx >= items.Count))
2990 int retVal = selectedIndex;
2993 retVal = OnUiRead(cb, () => cb.SelectedIndex, retVal);
2999 if ((0 > value) || (value >= items.Count))
return;
3001 selectedIndex = value;
3004 OnUi(cb, () => cb.SelectedIndex = value);
3043 private System.Drawing.Image _image;
3044 private PictureBoxSizeMode _sizeMode = PictureBoxSizeMode.Zoom;
3046 internal GPALPicture()
3055 public System.Drawing.Image
Image => _image;
3071 SyncImageToControl();
3086 if (
true == System.IO.File.Exists(file?.
Filename))
3087 using (var stream =
new System.IO.MemoryStream(System.IO.File.ReadAllBytes(file.
Filename)))
3088 _image =
new Bitmap(System.Drawing.Image.FromStream(stream));
3090 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Image file not found for picture [{file?.Filename}]",
this, GPALObjectType.Other);
3092 SyncImageToControl();
3103 _sizeMode = sizeMode;
3104 SyncImageToControl();
3111 private void SyncImageToControl()
3116 box.SizeMode = _sizeMode;
3142 private int _value = 0;
3143 private int _minimum = 0;
3144 private int _maximum = 100;
3145 private int _tickFrequency = 10;
3148 internal GPALSlider()
3163 _value = value < _minimum ? _minimum : value > _maximum ? _maximum : value;
3231 _tickFrequency = tickFrequency;
3244 _orientation = orientation;
3254 private void SyncToControl()
3259 bar.Minimum = _minimum;
3260 bar.Maximum = _maximum;
3261 bar.TickFrequency = 1 > _tickFrequency ? 1 : _tickFrequency;
3262 bar.Orientation = _orientation;
3270 private int _value = 0;
3271 private int _minimum = 0;
3272 private int _maximum = 100;
3273 private ProgressBarStyle _style = ProgressBarStyle.Blocks;
3275 internal GPALProgressBar()
3300 if (_value < min) _value = min;
3301 SyncValueToControl();
3313 if (_value > max) _value = max;
3314 SyncValueToControl();
3344 private void SyncValueToControl()
3348 OnUi(pb, () => Apply(pb));
3359 private void Apply(ProgressBar pb)
3363 if (pb.Maximum < _maximum)
3365 pb.Maximum = _maximum;
3366 pb.Minimum = _minimum;
3370 pb.Minimum = _minimum;
3371 pb.Maximum = _maximum;
3382 private void SyncStyleToControl()
3386 OnUi(pb, () => pb.Style = _style);
3400 _value = Math.Max(_minimum, Math.Min(_maximum, value));
3401 SyncValueToControl();
3422 SyncStyleToControl();
3442 private string _text =
"";
3444 internal GPALStatusStrip()
3459 _text = value ??
"";
3460 SyncTextToControl();
3481 private void SyncTextToControl()
3483 if (
WindowsControl is StatusStrip strip &&
true == Alive(strip) && strip.Items.Count > 0)
3485 ToolStripStatusLabel label = strip.Items[0] as ToolStripStatusLabel;
3488 PostToUi(strip, () => label.Text = _text);
3513 internal System.Drawing.Image Image {
get;
set; }
3515 internal GPALBarItem()
3541 return WindowsControl is ToolStripItem strip ? strip.Enabled : IsEnabled;
3551 Control owner = strip.Owner;
3553 OnUiOrHere(owner, () => strip.Enabled = value);
3588 string path = file?.
Filenames?.FirstOrDefault() ?? (string)file;
3589 if (!
string.IsNullOrWhiteSpace(path) && System.IO.File.Exists(path))
3590 this.Image = System.Drawing.Image.FromFile(path);
3592 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Image file not found for bar item [{path}]",
this, GPALObjectType.Other);
3594 catch (Exception ex)
3596 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to load bar item image [{file?.Filename}]",
this, GPALObjectType.Other, ex);
3622 private string _currentMenu;
3623 internal readonly List<(
string TopMenu,
GPALBarItem Item,
bool IsSeparator)> menuEntries
3626 internal GPALMenuBar()
3639 _currentMenu = topMenu;
3650 menuEntries.Add((_currentMenu, item,
false));
3660 menuEntries.Add((_currentMenu,
null,
true));
3681 internal readonly List<(
GPALBarItem Item,
bool IsSeparator)> toolbarEntries
3684 internal GPALToolbar()
3696 toolbarEntries.Add((item,
false));
3706 toolbarEntries.Add((
null,
true));
3727 private readonly List<string> columnHeaders =
new List<string>();
3728 private readonly List<double> columnWeights =
new List<double>();
3729 private ListViewMode viewMode = ListViewMode.Details;
3730 internal bool checkBoxes =
false;
3731 private bool fullRowSelect =
true;
3732 private bool multiSelect =
false;
3733 private bool sortable =
false;
3735 internal GPALListView()
3764 return WindowsControl is ListView lv ? lv.CheckBoxes : checkBoxes;
3770 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
3772 OnUi(lv, () => lv.CheckBoxes = value);
3778 if (headers !=
null)
3780 columnHeaders.AddRange(headers);
3803 fullRowSelect = enable;
3814 multiSelect = enable;
3823 this.sortable = sortable;
3833 columnWeights.Add(weight);
3840 public IReadOnlyList<string>
Columns => columnHeaders.AsReadOnly();
3858 case ListViewMode.List:
return View.List;
3859 case ListViewMode.LargeIcon:
return View.LargeIcon;
3860 case ListViewMode.SmallIcon:
return View.SmallIcon;
3861 case ListViewMode.Tile:
return View.Tile;
3862 default:
return View.Details;
3875 public int SelectedIndex
3882 retVal = OnUiRead(lv, () => 0 < lv.SelectedItems.Count ? lv.SelectedItems[0].Index : -1, retVal);
3888 public void ClearItems()
3890 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
3891 OnUi(lv, () => lv.Items.Clear());
3901 SetItemEnabled(index,
false);
3910 SetItemEnabled(index,
true);
3922 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return retVal;
3924 MethodInvoker read = () =>
3926 if (0 <= index && index < lv.Items.Count)
3927 retVal = SystemColors.GrayText != lv.Items[index].ForeColor;
3936 private void SetItemEnabled(
int index,
bool enabled)
3938 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
3940 MethodInvoker paint = () =>
3942 if (0 > index || index >= lv.Items.Count)
return;
3944 lv.Items[index].ForeColor =
true == enabled ? SystemColors.WindowText : SystemColors.GrayText;
3946 if (
false == enabled) lv.Items[index].Selected =
false;
3952 public void AddItem(
string title, params
string[] subItems)
3959 pendingRows.Enqueue(
new Tuple<
string,
string[]>(title, subItems ??
new string[0]));
3963 var item =
new ListViewItem(title);
3964 foreach (var s
in subItems) item.SubItems.Add(s);
3965 OnUi(lv, () => lv.Items.Add(item));
3969 private readonly System.Collections.Concurrent.ConcurrentQueue<Tuple<string, string[]>> pendingRows =
3970 new System.Collections.Concurrent.ConcurrentQueue<Tuple<string, string[]>>();
3975 internal override void Realized()
3977 while (
true == pendingRows.TryDequeue(out Tuple<
string,
string[]> held))
3978 AddItem(held.Item1, held.Item2);
3984 lv.HandleCreated += (sender, args) => LayOutColumns();
3985 lv.VisibleChanged += (sender, args) => LayOutColumns();
3986 lv.Resize += (sender, args) => LayOutColumns();
4010 columnWeights.Clear();
4013 columnWeights.AddRange(shares);
4019 internal readonly List<int> _columnWidths =
new List<int>();
4041 _columnWidths.Clear();
4044 _columnWidths.AddRange(widths);
4046 ApplyColumnWidths();
4054 internal bool LastColumnFills => 0 == columnWeights.Count;
4058 internal const int FillMargin = 2;
4061 private const int NarrowestColumn = 24;
4064 private bool layingOut =
false;
4078 internal void LayOutColumns()
4080 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv) || 0 < columnWeights.Count)
4083 if (
true == lv.InvokeRequired)
4085 OnUi(lv, LayOutColumns);
4089 if (
false == lv.IsHandleCreated || 0 == lv.Columns.Count ||
true == layingOut)
4096 int available = Math.Max(NarrowestColumn * lv.Columns.Count, lv.ClientSize.Width - FillMargin);
4097 int[] widths =
new int[lv.Columns.Count];
4103 for (
int column = 0; column < widths.Length; column++)
4104 if (column < _columnWidths.Count && 0 < _columnWidths[column])
4106 widths[column] = _columnWidths[column];
4107 named += widths[column];
4114 int each = Math.Max(NarrowestColumn, (available - named) / unnamed);
4116 for (
int column = 0; column < widths.Length; column++)
4117 if (0 == widths[column])
4118 widths[column] = each;
4125 foreach (
int width
in widths)
4128 if (available < total && 0 < total)
4130 for (
int column = 0; column < widths.Length; column++)
4131 widths[column] = Math.Max(NarrowestColumn, widths[column] * available / total);
4135 foreach (
int width
in widths)
4140 widths[widths.Length - 1] += available - total;
4142 if (NarrowestColumn > widths[widths.Length - 1])
4143 widths[widths.Length - 1] = NarrowestColumn;
4145 for (
int column = 0; column < widths.Length; column++)
4146 lv.Columns[column].Width = widths[column];
4156 internal void ApplyColumnWidths()
4168 string retVal =
null;
4173 retVal = OnUiRead(lv, () => 0 < lv.SelectedItems.Count ? lv.SelectedItems[0].Text :
null, retVal);
4191 List<bool> retVal =
new List<bool>();
4194 retVal = OnUiRead(lv, () =>
4196 List<bool> ticked =
new List<bool>();
4198 foreach (ListViewItem item
in lv.Items)
4199 ticked.Add(item.Checked);
4204 return retVal.AsReadOnly();
4220 List<bool> retVal =
new List<bool>();
4223 retVal = OnUiRead(lv, () =>
4225 List<bool> chosen =
new List<bool>();
4227 foreach (ListViewItem item
in lv.Items)
4228 chosen.Add(item.Selected);
4233 return retVal.AsReadOnly();
4252 private decimal _value = 0m;
4253 private decimal _minimum = 0m;
4254 private decimal _maximum = 100m;
4255 private decimal _increment = 1m;
4256 private int _decimalPlaces = 0;
4258 internal GPALNumericUpDown()
4314 _decimalPlaces = places;
4327 decimal retVal = _value;
4330 retVal = OnUiRead(nud, () => nud.Value, retVal);
4338 OnUi(nud, () => nud.Value = value);
4376 private DateTime _value = DateTime.Now;
4377 private DateTimePickerFormat _format = DateTimePickerFormat.Long;
4378 private string _customFormat =
"";
4380 internal GPALDateTimePicker()
4416 _customFormat = formatString;
4429 DateTime retVal = _value;
4432 retVal = OnUiRead(dtp, () => dtp.Value, retVal);
4440 OnUi(dtp, () => dtp.Value = value);
4451 public DateTimePickerFormat
Format => _format;
4469 private bool _checkBoxes =
false;
4470 private bool _showLines =
true;
4471 private bool _showPlusMinus =
true;
4473 internal GPALTreeView()
4485 _checkBoxes = enable;
4507 _showPlusMinus = show;
4529 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
4530 OnUi(tv, () => tv.Nodes.Clear());
4541 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return null;
4543 TreeNode node =
null;
4545 OnUi(tv, () => node = tv.SelectedNode);
4551 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
4553 MethodInvoker select = () => tv.SelectedNode = value?.Node;
4565 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
4567 OnUi(tv, () => tv.BeginUpdate());
4575 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
4577 OnUi(tv, () => tv.EndUpdate());
4587 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return null;
4588 TreeNode node =
null;
4589 OnUi(tv, () => node = tv.Nodes.Add(text));
4598 public class GPALTreeNode
4600 private readonly TreeNode _node;
4602 internal GPALTreeNode(TreeNode node) { _node = node; }
4605 internal TreeNode Node => _node;
4614 TreeView tv = _node.TreeView;
4615 string retVal =
null;
4617 GPALControl.OnUiOrHere(tv, () => retVal = _node.Text);
4623 TreeView tv = _node.TreeView;
4625 GPALControl.OnUiOrHere(tv, () => _node.Text = value);
4635 get {
return _node.Tag; }
4636 set { _node.Tag = value; }
4644 TreeView tv = _node.TreeView;
4646 if (
null == tv)
return;
4648 GPALControl.OnUiOrHere(tv, () => tv.SelectedNode = _node);
4656 TreeView tv = _node.TreeView;
4658 GPALControl.OnUiOrHere(tv, () => _node.Collapse());
4668 TreeNode child =
null;
4669 TreeView tv = _node.TreeView;
4670 GPALControl.OnUiOrHere(tv, () => child = _node.Nodes.Add(text));
4671 return child !=
null ?
new GPALTreeNode(child) :
null;
4684 return SystemColors.GrayText != _node.ForeColor;
4688 TreeView tv = _node.TreeView;
4692 MethodInvoker paint = () => _node.ForeColor =
true == value ? SystemColors.WindowText : SystemColors.GrayText;
4703 TreeView tv = _node.TreeView;
4737 if (selectedPaths ==
null || selectedPaths.Length == 0)
4764 internal string _title =
"Select File/Folder";
4765 internal string _initialDirectory =
"";
4766 internal string _filter =
"All files (*.*)|*.*";
4767 internal bool _selectFolder =
false;
4768 internal bool _multiSelect =
false;
4769 internal bool _compact =
false;
4770 internal string _placeholder =
"";
4771 internal string _selectedPath =
"";
4772 internal readonly List<string> _selectedPaths =
new List<string>();
4774 internal GPALFileSelector()
4786 _title =
string.IsNullOrWhiteSpace(title) ?
"Select File/Folder" : title;
4797 _initialDirectory = path;
4809 _filter =
string.IsNullOrWhiteSpace(filter) ?
"All files (*.*)|*.*" : filter;
4821 _selectFolder = folderMode;
4833 _multiSelect = allowMulti;
4853 _placeholder = placeholder ??
"";
4875 _selectedPath = path ??
"";
4877 _selectedPaths.Clear();
4879 if (
false ==
string.IsNullOrEmpty(_selectedPath))
4880 _selectedPaths.Add(_selectedPath);
4885 public string SelectedPath => _selectedPath;
4896 internal void UpdateSelection(
string[] paths)
4898 _selectedPaths.Clear();
4899 if (paths !=
null && paths.Length > 0)
4901 _selectedPaths.AddRange(paths);
4902 _selectedPath = paths[0];
4918 internal void NotifySelection(
bool cancelled,
string[] paths)
4920 UpdateSelection(paths);
4924 foreach (var (evt, handler) in Callbacks)
4926 ControlEventType target = evt == ControlEventType.Default ? ControlEventType.Change : evt;
4928 if (target == ControlEventType.Change ||
4929 target == ControlEventType.Click ||
4930 target == ControlEventType.Default)
4932 ((EventHandler)handler)?.Invoke(
this, args);
4939 _selectedPaths.Clear();
4942 PostToUi(ctrl, () => ctrl.Text =
"");
4946 public void SetPath(
string path)
4948 _selectedPath = path ??
"";
4949 _selectedPaths.Clear();
4950 if (!
string.IsNullOrEmpty(_selectedPath))
4951 _selectedPaths.Add(_selectedPath);
4954 PostToUi(ctrl, () => ctrl.Text = _selectedPath);
4958 private void SyncDisplay()
4962 string display = _selectedPaths.Count > 0
4963 ?
string.Join(
"; ", _selectedPaths)
4966 PostToUi(ctrl, () => ctrl.Text = display);
4971 #endregion <GPAL Controls>
File-side plumbing behind the fluent chain: writing a unit of work's data out in a delimited format,...
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
List< string > Filenames
Get the list of filenames.
string Filename
We have only one file, accessing it.
override IAllowControlSettings WithReadOnly(bool readOnly=true)
Whether the user can change what the control holds. Every control takes it; the ones that hold nothin...
new string Text
The Markdown source text. Setting this re-renders headings, bold spans, and bullet/numbered lists (wi...
new IAllowRichTextBoxControlSettings WithText(string text)
Sets the initial Markdown source text.
void AppendLine(string text, int maxLines=0)
Appends text as a new line, thread-safe. The line is appended verbatim rather than rendered as Mark...
void Clear()
Empties the control and anything queued for it, thread-safe.
void ScrollToEnd()
Scrolls to the last line, thread-safe.
override IAllowControlSettings WithReadOnly(bool readOnly=true)
Whether the user can change what the control holds. Every control takes it; the ones that hold nothin...
void Clear()
Empties the control and anything queued for it, thread-safe.
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.
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....
static IAllowGridActions< string > Grid
New GPALGrid<string></string> (rows/columns).
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...