18using System.Collections.Concurrent;
19using System.Collections.Generic;
23using System.Text.RegularExpressions;
24using System.Threading.Tasks;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
37 #region <GPAL Controls>
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;
92 throw new NotImplementedException();
109 public int Height {
get;
internal set; } = 0;
110 private string _text;
124 OnUi(ctrl, () => ctrl.Text = value);
132 public string Name {
get;
internal set; }
136 public object Tag {
get;
internal set; }
154 internal static bool Alive(Control control)
160 return null != control
161 &&
false == control.IsDisposed
162 &&
false == control.Disposing;
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;
179 internal System.Windows.Forms.Keys ShortCutKey {
get;
set; }
180 internal List<(ControlEventType EventType, Delegate Handler)> Callbacks {
get; } =
new List<(ControlEventType EventType, Delegate Handler)>();
199 where TDelegate : Delegate
201 if (handler is EventHandler typed)
202 Callbacks.Add((ControlEventType.Default, (EventHandler)((sender, args) => RunOnUiThread(typed, sender, args))));
204 Callbacks.Add((ControlEventType.Default, handler));
214 private const double UiHeldWarningSeconds = 2.0;
220 private int _callbackDepth;
227 private int _callbackPending;
233 private const int CallbackBacklogWarning = 5;
239 private System.Threading.Tasks.Task _callbackChain = System.Threading.Tasks.Task.CompletedTask;
245 private readonly
object _callbackChainGate =
new object();
251 private string CallbackOwner =>
true ==
string.IsNullOrEmpty(
Name) ? GetType().Name :
Name;
258 private void EnterCallback()
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);
271 private void RunOnUiThread(EventHandler handler,
object sender, EventArgs args)
273 System.Diagnostics.Stopwatch held = System.Diagnostics.Stopwatch.StartNew();
279 handler(sender, args);
284 System.Threading.Interlocked.Decrement(ref _callbackDepth);
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);
320 where TDelegate : Delegate
322 if (handler is EventHandler typed)
324 EventHandler onItsOwnThread = (sender, args) =>
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);
329 lock (_callbackChainGate)
330 _callbackChain = _callbackChain.ContinueWith(previous => RunInBackground(typed, sender, args));
333 Callbacks.Add((ControlEventType.Default, onItsOwnThread));
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);
341 Callbacks.Add((ControlEventType.Default, handler));
354 private void RunInBackground(EventHandler handler,
object sender, EventArgs args)
360 handler(sender, args);
366 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"A background handler for [{CallbackOwner}] ended in an exception",
this, GPALObjectType.Other, ex);
370 System.Threading.Interlocked.Decrement(ref _callbackDepth);
371 System.Threading.Interlocked.Decrement(ref _callbackPending);
386 if (Callbacks.Count > 0)
388 var last = Callbacks[Callbacks.Count - 1];
389 if (last.EventType == ControlEventType.Default)
391 Callbacks[Callbacks.Count - 1] = (eventType, last.Handler);
397 Callbacks.Add((eventType,
null));
407 ToolTipText = toolTipText ??
"";
418 ToolTipAutoPopDelayMs = Math.Max(1000, delayInMs);
438 ShortCutKey = shortCutKey;
451 internal static void OnUi(Control control, MethodInvoker work)
453 if (
false == Alive(control))
460 if (
true == control.IsHandleCreated &&
true == control.InvokeRequired)
461 control.Invoke(work);
465 catch (ObjectDisposedException)
469 catch (InvalidOperationException)
483 internal static void OnUiOrHere(Control control, MethodInvoker work)
485 if (
true == Alive(control))
498 internal static void PostToUi(Control control, MethodInvoker work)
500 if (
false == Alive(control))
505 if (
true == control.IsHandleCreated &&
true == control.InvokeRequired)
506 control.BeginInvoke(work);
510 catch (ObjectDisposedException)
514 catch (InvalidOperationException)
530 internal static T OnUiRead<T>(Control control, Func<T> read, T whenGone)
534 if (
true == Alive(control))
537 retVal =
true == control.IsHandleCreated &&
true == control.InvokeRequired
538 ? (T)control.Invoke(read)
541 catch (ObjectDisposedException)
545 catch (InvalidOperationException)
613 this.Height = height;
702 internal ChartColorPalette ChartColorPalette {
get;
set; }
703 internal Series mySeries {
get;
set; }
704 internal string Title {
get;
set; }
709 Chart chart =
new Chart
713 Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
714 Dock = DockStyle.Fill,
716 chart.ChartAreas.Add(
new ChartArea(
Name));
783 internal ChartAreaCollection ChartAreas
829 internal bool IsDefault {
get;
set; }
830 internal GPALButton()
843 if (
null != ((Button)this.WindowsControl))
844 return ((Button)this.WindowsControl).Enabled;
852 OnUi(btn, () => btn.Enabled = value);
878 this.Enabled = enabled;
902 internal GPALCheckbox()
913 this.Checked = isChecked;
925 bool retVal = IsChecked;
928 retVal = OnUiRead(cb, () => cb.Checked, retVal);
936 OnUi(cb, () => cb.Checked = value);
952 if (
null != ((CheckBox)this.WindowsControl))
953 return ((CheckBox)this.WindowsControl).Enabled;
961 OnUi(cbx, () => cbx.Enabled = value);
974 this.Enabled = enabled;
997 private IGPALGrid<string> _grid;
998 private readonly List<string> _columnNames =
new List<string>();
999 private bool _autoGenerateColumns =
false;
1001 internal GPALDataGridView()
1014 if (columnNames !=
null)
1016 _columnNames.AddRange(columnNames);
1028 base.ReadOnly = readOnly;
1040 _autoGenerateColumns = autoGenerate;
1052 this.CurrentGrid = grid;
1064 internal void SyncGridToControl()
1066 if (!(
WindowsControl is DataGridView dgv) ||
false == Alive(dgv) || _grid ==
null)
1071 if (dgv.InvokeRequired)
1073 OnUi(dgv, SyncGridToControl);
1078 dgv.Columns.Clear();
1081 int columnCount = 0;
1082 foreach (List<string> row
in _grid)
1086 columnCount = row.Count;
1092 if (_columnNames.Count > 0)
1095 foreach (
string header
in _columnNames)
1097 if (colIndex >= columnCount)
break;
1098 dgv.Columns.Add(header, header);
1102 else if (_autoGenerateColumns && columnCount > 0)
1104 for (
int i = 0; i < columnCount; i++)
1106 string header = $
"Column {i + 1}";
1107 dgv.Columns.Add(header, header);
1112 foreach (List<string> rowData
in _grid)
1114 if (rowData ==
null)
continue;
1116 var dgvRow =
new DataGridViewRow();
1117 var cells =
new DataGridViewCell[rowData.Count];
1120 foreach (
string cellValue
in rowData)
1122 cells[colIndex] =
new DataGridViewTextBoxCell
1124 Value = cellValue ??
""
1129 dgvRow.Cells.AddRange(cells);
1130 dgv.Rows.Add(dgvRow);
1133 dgv.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
1134 dgv.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);
1147 SyncGridToControl();
1153 public IReadOnlyList<string>
ColumnNames => _columnNames.AsReadOnly();
1179 internal bool InputIsPassword {
get;
set; } =
false;
1180 internal bool IsReadOnly {
get;
set; } =
false;
1181 internal string PlaceHolder {
get;
set; }
1182 internal GPALInput()
1203 OnUi(ptbInput, () => ptbInput.Enabled = value);
1216 this.Enabled = enabled;
1233 return false == InputIsPassword;
1237 InputIsPassword =
false == value;
1241 char passwordChar =
true == value ?
'\0' :
'●';
1243 OnUi(ptbInput, () => ptbInput.PasswordChar = passwordChar);
1252 InputIsPassword =
true;
1253 if (
true == String.IsNullOrEmpty(PlaceHolder))
1254 PlaceHolder =
"Enter Password";
1258 public IAllowControlSettings WithPlaceholder(
string PlaceholderText)
1260 PlaceHolder = PlaceholderText;
1261 if (
WindowsControl is PlaceholderTextBox tb &&
true == Alive(tb))
1262 tb.PlaceholderText = PlaceholderText;
1266 public IAllowControlSettings WithReadOnly(
bool readOnly =
true)
1268 IsReadOnly = readOnly;
1269 if (
WindowsControl is PlaceholderTextBox tb &&
true == Alive(tb))
1270 tb.ReadOnly = readOnly;
1298 internal GPALLabel()
1331 internal GPALRadioButton()
1342 this.Checked = isChecked;
1354 bool retVal = IsChecked;
1357 retVal = OnUiRead(rb, () => rb.Checked, retVal);
1365 OnUi(rb, () => rb.Checked = value);
1381 if (
null != ((RadioButton)this.WindowsControl))
1382 return ((RadioButton)this.WindowsControl).Enabled;
1390 OnUi(rb2, () => rb2.Enabled = value);
1403 this.Enabled = enabled;
1442 return WindowsControl is TabPage page ? page.Enabled : IsEnabled;
1448 if (!(
WindowsControl is TabPage page) ||
false == Alive(page))
return;
1450 OnUi(page, () => page.Enabled = value);
1460 this.Enabled = enabled;
1468 if (
WindowsControl is TabPage tabPage && tabPage.Parent is TabControl tabControl)
1469 tabControl.SelectedTab = tabPage;
1478 public class GPALSpliter
1480 internal GPALSpliter() { }
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>();
1513 internal GPALSplitter() { }
1532 IsHorizontalSplit =
true;
1533 Dimension = leftWidth;
1544 IsHorizontalSplit =
true;
1545 Percentage = leftPercent;
1555 IsHorizontalSplit =
false;
1556 Dimension = topHeight;
1567 IsHorizontalSplit =
false;
1568 Percentage = topPercent;
1579 FormControls.Add(gPALFormControl);
1605 internal List<object> FormControls {
get; } =
new List<object>();
1607 internal GPALGroupBox() { }
1626 FormControls.Add(gPALFormControl);
1645 internal GPALTableLayoutPanel()
1657 if (
null != ((TableLayoutPanel)this.WindowsControl))
1658 return ((TableLayoutPanel)this.WindowsControl).Enabled;
1664 if (
null != ((TableLayoutPanel)this.WindowsControl))
1665 ((TableLayoutPanel)this.WindowsControl).
Enabled = value;
1679 if (
null != ((TableLayoutPanel)this.WindowsControl))
1680 return ((TableLayoutPanel)this.WindowsControl).GrowStyle;
1686 if (
null != ((TableLayoutPanel)this.WindowsControl))
1687 ((TableLayoutPanel)this.WindowsControl).
GrowStyle = value;
1700 if (
null != ((TableLayoutPanel)this.WindowsControl))
1701 return ((TableLayoutPanel)this.WindowsControl).ColumnCount;
1707 if (
null != ((TableLayoutPanel)this.WindowsControl))
1708 ((TableLayoutPanel)this.WindowsControl).
ColumnCount = value;
1722 if (
null != ((TableLayoutPanel)this.WindowsControl))
1723 return ((TableLayoutPanel)this.WindowsControl).RowCount;
1729 if (
null != ((TableLayoutPanel)this.WindowsControl))
1730 ((TableLayoutPanel)this.WindowsControl).
RowCount = value;
1755 this.Enabled = enabled;
1766 this.GrowStyle = tableLayoutPanelGrowStyle;
1800 internal bool IsReadOnly {
get;
set; } =
false;
1801 internal string PlaceHolder {
get;
set; }
1804 private readonly ConcurrentQueue<string> pendingLines =
new ConcurrentQueue<string>();
1805 private readonly
object appendGate =
new object();
1806 private bool waitingForHandle =
false;
1808 internal GPALTextArea()
1829 OnUi(ptbArea, () => ptbArea.Enabled = value);
1842 this.Enabled = enabled;
1848 PlaceHolder = PlaceholderText;
1850 tb.PlaceholderText = PlaceholderText;
1854 public IAllowControlSettings WithReadOnly(
bool readOnly =
true)
1856 IsReadOnly = readOnly;
1857 if (
WindowsControl is PlaceholderTextBox tb &&
true == Alive(tb))
1858 tb.ReadOnly = readOnly;
1875 pendingLines.Enqueue(text);
1886 private void Drain(
int maxLines)
1896 if (
false == tb.IsHandleCreated)
1900 if (
false == waitingForHandle)
1902 waitingForHandle =
true;
1903 tb.HandleCreated += (sender, args) => Drain(maxLines);
1910 MethodInvoker drain = () =>
1913 if (
false == Alive(tb) ||
false == tb.IsHandleCreated)
return;
1915 while (
true == pendingLines.TryDequeue(out
string line))
1917 if (maxLines > 0 && tb.Lines.Length >= maxLines)
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);
1924 if (tb.TextLength > 0) tb.AppendText(
"\r\n");
1925 tb.AppendText(line);
1933 PostToUi(tb, drain);
1945 while (
true == pendingLines.TryDequeue(out
string dropped)) { }
1949 if (
false == tb.IsHandleCreated)
1951 tb.Text =
string.Empty;
1955 MethodInvoker clear = () =>
1957 if (
false == Alive(tb) ||
false == tb.IsHandleCreated)
return;
1962 PostToUi(tb, clear);
1971 if (
false == Alive(tb))
return;
1973 PostToUi(tb, () => tb.ScrollToCaret());
1993 private string _text =
string.Empty;
1994 internal bool IsReadOnly {
get;
set; } =
true;
1996 internal GPALRichTextBox()
2011 _text = value ??
string.Empty;
2014 OnUi(rtb, () => RenderMarkdown(rtb, _text));
2047 OnUi(rtb, () => rtb.Enabled = value);
2063 this.Enabled = enabled;
2069 IsReadOnly = readOnly;
2071 rtb.ReadOnly = readOnly;
2076 private static readonly Regex _inlinePattern =
new Regex(
2077 @"\*\*(?<b>.+?)\*\*|\*(?<i>.+?)\*|__(?<u>.+?)__|~~(?<s>.+?)~~|`(?<code>.+?)`|{(?<c>[^}]+)}(?<ct>.+?)\{\}",
2078 RegexOptions.Compiled);
2088 internal static void RenderMarkdown(RichTextBox rtb,
string markdown)
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');
2097 foreach (
string rawLine
in lines)
2099 string line = rawLine;
2101 int headingLevel = 0;
2102 while (headingLevel < line.Length && line[headingLevel] ==
'#')
2104 if (headingLevel > 0 && headingLevel <= 3 && headingLevel < line.Length && line[headingLevel] ==
' ')
2105 line = line.Substring(headingLevel + 1);
2109 int leadingSpaces = 0;
2110 while (leadingSpaces < line.Length && line[leadingSpaces] ==
' ')
2112 int indentLevel = leadingSpaces / 2;
2113 string trimmed = line.Substring(leadingSpaces);
2116 if (trimmed ==
"---")
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");
2126 bool isBulletItem =
false;
2127 bool isListItem =
false;
2128 if (trimmed.StartsWith(
"- ") || trimmed.StartsWith(
"* "))
2130 isBulletItem =
true;
2132 trimmed = trimmed.Substring(2);
2136 Match numberedMatch = Regex.Match(trimmed,
@"^(\d+\.\s+)(.*)$");
2137 if (numberedMatch.Success)
2140 trimmed = numberedMatch.Groups[1].Value + numberedMatch.Groups[2].Value;
2144 int lineStart = rtb.TextLength;
2146 FontStyle headingBase = headingLevel > 0 ? FontStyle.Bold : FontStyle.Regular;
2147 float headingSize = headingLevel > 0 ? baseFont.Size + (4 - headingLevel) * 2 : baseFont.Size;
2149 foreach ((
string segmentText, FontStyle style, Color? color,
bool isCode) in SplitInline(trimmed, headingBase))
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);
2159 rtb.AppendText(
"\n");
2163 rtb.SelectionStart = lineStart;
2164 rtb.SelectionLength = rtb.TextLength - lineStart;
2165 rtb.SelectionBullet = isBulletItem;
2166 rtb.SelectionIndent = 20 * (indentLevel + 1);
2170 rtb.SelectionStart = 0;
2171 rtb.SelectionLength = 0;
2180 private static List<(
string Text, FontStyle Style, Color? Color,
bool IsCode)> SplitInline(
string line, FontStyle inheritBase = FontStyle.Regular)
2182 var result =
new List<(string Text, FontStyle Style, Color? Color, bool IsCode)>();
2185 foreach (Match m
in _inlinePattern.Matches(line))
2188 result.Add((line.Substring(pos, m.Index - pos), inheritBase,
null,
false));
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)
2202 Color named = Color.FromName(m.Groups[
"c"].Value);
2203 Color? resolved = named.IsKnownColor || named.A > 0 ? (Color?)named : null;
2205 foreach (var inner
in SplitInline(m.Groups[
"ct"].Value, inheritBase))
2206 result.Add((inner.Item1, inner.Item2, inner.Item3 ?? resolved, inner.Item4));
2209 pos = m.Index + m.Length;
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));
2239 internal readonly List<string> items =
new List<string>();
2240 internal int selectedIndex = -1;
2241 internal string selectedItem =
null;
2245 internal readonly HashSet<string> disabledItems =
new HashSet<string>();
2248 internal int lastEnabledIndex = -1;
2250 internal GPALComboBox()
2262 if (
null != itemArray)
2264 items.AddRange(itemArray);
2265 ShowItems(itemArray);
2273 private void ShowItems(IEnumerable<string> itemsToShow)
2275 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2277 object[] adding = itemsToShow.Cast<
object>().ToArray();
2279 OnUi(cb, () => cb.Items.AddRange(adding));
2290 if (
true ==
string.IsNullOrEmpty(item))
return;
2292 disabledItems.Add(item);
2302 if (
true ==
string.IsNullOrEmpty(item))
return;
2304 disabledItems.Remove(item);
2315 return false == disabledItems.Contains(item);
2318 private void Redraw()
2320 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2322 OnUi(cb, () => cb.Invalidate());
2332 disabledItems.Clear();
2334 selectedItem =
null;
2335 lastEnabledIndex = -1;
2337 if (!(
WindowsControl is ComboBox cb) ||
false == Alive(cb))
return;
2339 OnUi(cb, () => cb.Items.Clear());
2349 if (
null != itemCollection)
2351 items.AddRange(itemCollection);
2364 if (
null != itemArray)
2366 foreach (
object item
in itemArray)
2367 items.Add(item?.ToString() ??
string.Empty);
2380 selectedIndex = index;
2381 selectedItem =
null;
2393 selectedItem = name;
2406 int idx = SelectedIndex;
2407 if ((0 > idx) || (idx >= items.Count))
2427 bool retVal = IsEnabled;
2430 retVal = OnUiRead(cb, () => cb.Enabled, retVal);
2438 OnUi(cb, () => cb.Enabled = value);
2447 public int SelectedIndex
2453 int retVal = selectedIndex;
2456 retVal = OnUiRead(cb, () => cb.SelectedIndex, retVal);
2462 if ((0 > value) || (value >= items.Count))
return;
2464 selectedIndex = value;
2467 OnUi(cb, () => cb.SelectedIndex = value);
2490 private int _value = 0;
2491 private int _minimum = 0;
2492 private int _maximum = 100;
2493 private ProgressBarStyle _style = ProgressBarStyle.Blocks;
2495 internal GPALProgressBar()
2520 if (_value < min) _value = min;
2521 SyncValueToControl();
2533 if (_value > max) _value = max;
2534 SyncValueToControl();
2564 private void SyncValueToControl()
2568 OnUi(pb, () => Apply(pb));
2579 private void Apply(ProgressBar pb)
2583 if (pb.Maximum < _maximum)
2585 pb.Maximum = _maximum;
2586 pb.Minimum = _minimum;
2590 pb.Minimum = _minimum;
2591 pb.Maximum = _maximum;
2602 private void SyncStyleToControl()
2606 OnUi(pb, () => pb.Style = _style);
2620 _value = Math.Max(_minimum, Math.Min(_maximum, value));
2621 SyncValueToControl();
2642 SyncStyleToControl();
2662 private string _text =
"";
2664 internal GPALStatusStrip()
2679 _text = value ??
"";
2680 SyncTextToControl();
2701 private void SyncTextToControl()
2703 if (
WindowsControl is StatusStrip strip &&
true == Alive(strip) && strip.Items.Count > 0)
2705 ToolStripStatusLabel label = strip.Items[0] as ToolStripStatusLabel;
2708 OnUi(strip, () => label.Text = _text);
2733 internal System.Drawing.Image Image {
get;
set; }
2735 internal GPALBarItem()
2761 return WindowsControl is ToolStripItem strip ? strip.Enabled : IsEnabled;
2771 Control owner = strip.Owner;
2773 OnUiOrHere(owner, () => strip.Enabled = value);
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);
2812 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Image file not found for bar item [{path}]",
this, GPALObjectType.Other);
2814 catch (Exception ex)
2816 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to load bar item image [{file?.Filename}]",
this, GPALObjectType.Other, ex);
2842 private string _currentMenu;
2843 internal readonly List<(
string TopMenu,
GPALBarItem Item,
bool IsSeparator)> menuEntries
2846 internal GPALMenuBar()
2859 _currentMenu = topMenu;
2870 menuEntries.Add((_currentMenu, item,
false));
2880 menuEntries.Add((_currentMenu,
null,
true));
2901 internal readonly List<(
GPALBarItem Item,
bool IsSeparator)> toolbarEntries
2904 internal GPALToolbar()
2916 toolbarEntries.Add((item,
false));
2926 toolbarEntries.Add((
null,
true));
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;
2955 internal GPALListView()
2984 return WindowsControl is ListView lv ? lv.CheckBoxes : checkBoxes;
2990 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
2992 OnUi(lv, () => lv.CheckBoxes = value);
2998 if (headers !=
null)
3000 columnHeaders.AddRange(headers);
3023 fullRowSelect = enable;
3034 multiSelect = enable;
3043 this.sortable = sortable;
3053 columnWeights.Add(weight);
3060 public IReadOnlyList<string>
Columns => columnHeaders.AsReadOnly();
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;
3095 public int SelectedIndex
3102 retVal = OnUiRead(lv, () => 0 < lv.SelectedItems.Count ? lv.SelectedItems[0].Index : -1, retVal);
3108 public void ClearItems()
3110 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
3111 OnUi(lv, () => lv.Items.Clear());
3121 SetItemEnabled(index,
false);
3130 SetItemEnabled(index,
true);
3142 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return retVal;
3144 MethodInvoker read = () =>
3146 if (0 <= index && index < lv.Items.Count)
3147 retVal = SystemColors.GrayText != lv.Items[index].ForeColor;
3156 private void SetItemEnabled(
int index,
bool enabled)
3158 if (!(
WindowsControl is ListView lv) ||
false == Alive(lv))
return;
3160 MethodInvoker paint = () =>
3162 if (0 > index || index >= lv.Items.Count)
return;
3164 lv.Items[index].ForeColor =
true == enabled ? SystemColors.WindowText : SystemColors.GrayText;
3166 if (
false == enabled) lv.Items[index].Selected =
false;
3172 public void AddItem(
string title, params
string[] subItems)
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));
3180 public void SetColumnWidth(
int index,
int width)
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);
3192 ? lv2.SelectedItems[0].Text
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;
3216 internal GPALNumericUpDown()
3272 _decimalPlaces = places;
3285 decimal retVal = _value;
3288 retVal = OnUiRead(nud, () => nud.Value, retVal);
3296 OnUi(nud, () => nud.Value = value);
3334 private DateTime _value = DateTime.Now;
3335 private DateTimePickerFormat _format = DateTimePickerFormat.Long;
3336 private string _customFormat =
"";
3338 internal GPALDateTimePicker()
3374 _customFormat = formatString;
3387 DateTime retVal = _value;
3390 retVal = OnUiRead(dtp, () => dtp.Value, retVal);
3398 OnUi(dtp, () => dtp.Value = value);
3409 public DateTimePickerFormat
Format => _format;
3427 private bool _checkBoxes =
false;
3428 private bool _showLines =
true;
3429 private bool _showPlusMinus =
true;
3431 internal GPALTreeView()
3443 _checkBoxes = enable;
3465 _showPlusMinus = show;
3487 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
3488 OnUi(tv, () => tv.Nodes.Clear());
3499 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return null;
3501 TreeNode node =
null;
3503 OnUi(tv, () => node = tv.SelectedNode);
3509 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
3511 MethodInvoker select = () => tv.SelectedNode = value?.Node;
3523 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
3525 OnUi(tv, () => tv.BeginUpdate());
3533 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return;
3535 OnUi(tv, () => tv.EndUpdate());
3545 if (!(
WindowsControl is TreeView tv) ||
false == Alive(tv))
return null;
3546 TreeNode node =
null;
3547 OnUi(tv, () => node = tv.Nodes.Add(text));
3556 public class GPALTreeNode
3558 private readonly TreeNode _node;
3560 internal GPALTreeNode(TreeNode node) { _node = node; }
3563 internal TreeNode Node => _node;
3572 TreeView tv = _node.TreeView;
3573 string retVal =
null;
3575 GPALControl.OnUiOrHere(tv, () => retVal = _node.Text);
3581 TreeView tv = _node.TreeView;
3583 GPALControl.OnUiOrHere(tv, () => _node.Text = value);
3593 get {
return _node.Tag; }
3594 set { _node.Tag = value; }
3602 TreeView tv = _node.TreeView;
3604 if (
null == tv)
return;
3606 GPALControl.OnUiOrHere(tv, () => tv.SelectedNode = _node);
3614 TreeView tv = _node.TreeView;
3616 GPALControl.OnUiOrHere(tv, () => _node.Collapse());
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;
3641 return SystemColors.GrayText != _node.ForeColor;
3645 TreeView tv = _node.TreeView;
3649 MethodInvoker paint = () => _node.ForeColor =
true == value ? SystemColors.WindowText : SystemColors.GrayText;
3660 TreeView tv = _node.TreeView;
3694 if (selectedPaths ==
null || selectedPaths.Length == 0)
3721 internal string _title =
"Select File/Folder";
3722 internal string _initialDirectory =
"";
3723 internal string _filter =
"All files (*.*)|*.*";
3724 internal bool _selectFolder =
false;
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>();
3731 internal GPALFileSelector()
3743 _title =
string.IsNullOrWhiteSpace(title) ?
"Select File/Folder" : title;
3754 _initialDirectory = path;
3766 _filter =
string.IsNullOrWhiteSpace(filter) ?
"All files (*.*)|*.*" : filter;
3778 _selectFolder = folderMode;
3790 _multiSelect = allowMulti;
3810 _placeholder = placeholder ??
"";
3828 internal void UpdateSelection(
string[] paths)
3830 _selectedPaths.Clear();
3831 if (paths !=
null && paths.Length > 0)
3833 _selectedPaths.AddRange(paths);
3834 _selectedPath = paths[0];
3850 internal void NotifySelection(
bool cancelled,
string[] paths)
3852 UpdateSelection(paths);
3856 foreach (var (evt, handler) in Callbacks)
3858 ControlEventType target = evt == ControlEventType.Default ? ControlEventType.Change : evt;
3860 if (target == ControlEventType.Change ||
3861 target == ControlEventType.Click ||
3862 target == ControlEventType.Default)
3864 ((EventHandler)handler)?.Invoke(
this, args);
3871 _selectedPaths.Clear();
3874 OnUi(ctrl, () => ctrl.Text =
"");
3878 public void SetPath(
string path)
3880 _selectedPath = path ??
"";
3881 _selectedPaths.Clear();
3882 if (!
string.IsNullOrEmpty(_selectedPath))
3883 _selectedPaths.Add(_selectedPath);
3886 OnUi(ctrl, () => ctrl.Text = _selectedPath);
3890 private void SyncDisplay()
3894 string display = _selectedPaths.Count > 0
3895 ?
string.Join(
"; ", _selectedPaths)
3898 OnUi(ctrl, () => ctrl.Text = display);
3903 #endregion <GPAL Controls>
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
List< string > Filenames
Get the list of filenames.
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.
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.
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 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...