From 258bb621b33c130c155f820139bd42bece0b15a6 Mon Sep 17 00:00:00 2001
From: "13997737+wolframhaussig@users.noreply.github.com"
<13997737+wolframhaussig@users.noreply.github.com>
Date: Wed, 9 Aug 2023 19:54:18 +0200
Subject: [PATCH 1/5] First proposal for DateTimePicker
---
.../FrmDefault.Designer.cs | 15 +-
.../FrmDefault.resx | 12 +
.../Controls/StylableDateTimePicker.cs | 298 ++++++++++++++++--
.../Extensions/ColorExtensions.cs | 58 ++++
4 files changed, 357 insertions(+), 26 deletions(-)
create mode 100644 StylableWinFormsControls/StylableWinFormsControls/Extensions/ColorExtensions.cs
diff --git a/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.Designer.cs b/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.Designer.cs
index 6ef81ff..0bf3ba9 100644
--- a/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.Designer.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.Designer.cs
@@ -28,11 +28,11 @@ protected override void Dispose(bool disposing)
///
private void InitializeComponent()
{
- ListViewItem listViewItem1 = new ListViewItem("Content");
- ListViewItem listViewItem2 = new ListViewItem("Content");
- ListViewItem listViewItem3 = new ListViewItem(new string[] { "Content", "Content" }, -1);
- ListViewItem listViewItem4 = new ListViewItem("Content");
- ListViewItem listViewItem5 = new ListViewItem("Content");
+ ListViewItem listViewItem6 = new ListViewItem("Content");
+ ListViewItem listViewItem7 = new ListViewItem("Content");
+ ListViewItem listViewItem8 = new ListViewItem(new string[] { "Content", "Content" }, -1);
+ ListViewItem listViewItem9 = new ListViewItem("Content");
+ ListViewItem listViewItem10 = new ListViewItem("Content");
stylableButton1 = new StylableButton();
stylableCheckBox1 = new StylableCheckBox();
stylableComboBox1 = new StylableComboBox();
@@ -171,7 +171,7 @@ private void InitializeComponent()
// stylableListView1
//
stylableListView1.Columns.AddRange(new ColumnHeader[] { columnHeader1, columnHeader2 });
- stylableListView1.Items.AddRange(new ListViewItem[] { listViewItem1, listViewItem2, listViewItem3, listViewItem4, listViewItem5 });
+ stylableListView1.Items.AddRange(new ListViewItem[] { listViewItem6, listViewItem7, listViewItem8, listViewItem9, listViewItem10 });
stylableListView1.Location = new Point(6, 18);
stylableListView1.Name = "stylableListView1";
stylableListView1.Size = new Size(242, 178);
@@ -225,7 +225,7 @@ private void InitializeComponent()
stylableTextBox1.BorderColor = Color.Blue;
stylableTextBox1.BorderStyle = BorderStyle.None;
stylableTextBox1.DelayedTextChangedTimeout = 900;
- stylableTextBox1.ForeColor = Color.Black;
+ stylableTextBox1.ForeColor = Color.Gray;
stylableTextBox1.Hint = "Hello, my name is ...";
stylableTextBox1.HintForeColor = Color.Gray;
stylableTextBox1.IsDelayActive = true;
@@ -233,6 +233,7 @@ private void InitializeComponent()
stylableTextBox1.Name = "stylableTextBox1";
stylableTextBox1.Size = new Size(207, 16);
stylableTextBox1.TabIndex = 8;
+ stylableTextBox1.Text = "Hello, my name is ...";
stylableTextBox1.TextForeColor = Color.Black;
//
// lbl_description
diff --git a/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.resx b/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.resx
index 266fef6..9563326 100644
--- a/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.resx
+++ b/StylableWinFormsControls/StylableWinFormsControls.Example/FrmDefault.resx
@@ -129,4 +129,16 @@
True
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index c4e5382..3be3187 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -1,5 +1,8 @@
-using System.ComponentModel;
+using StylableWinFormsControls.Extensions;
+using System.ComponentModel;
using System.Drawing.Text;
+using System.Globalization;
+using System.Text.RegularExpressions;
using System.Windows.Forms.VisualStyles;
namespace StylableWinFormsControls;
@@ -9,24 +12,35 @@ namespace StylableWinFormsControls;
///
public class StylableDateTimePicker : DateTimePicker
{
- public Color EnabledBackColor { get; set; } = Color.White;
- public Color DisabledBackColor { get; set; } = Color.Gray;
- public Color EnabledForeColor { get; set; } = Color.Black;
- public Color DisabledForeColor { get; set; } = Color.Black;
+ ///
+ /// separate two spaces with the given width
+ ///
+ public const int PART_SPACE = 5;
+
+ ///
+ /// contains all DateTime parts and their positions
+ ///
+ private readonly List _dateParts = new List();
+
+ ///
+ /// when the format is changed we need to recalculate the positions of the parts
+ ///
+ private string _oldFormat = "";
+
+ ///
+ /// when the width is changed we need to recalculate the positions of the parts
+ ///
+ private int _oldWidth = 0;
+
public StylableDateTimePicker()
{
this.SetStyle(ControlStyles.UserPaint, true);
+ this.MouseDown += StylableDateTimePicker_MouseDown;
+ this.Leave += StylableDateTimePicker_Leave;
+ this.MouseWheel += StylableDateTimePicker_MouseWheel;
+ this.KeyDown += StylableDateTimePicker_KeyDown;
}
- protected override CreateParams CreateParams
- {
- get
- {
- CreateParams handleParam = base.CreateParams;
- //prevent flickering of the control
- handleParam.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
- return handleParam;
- }
- }
+
///
/// Gets or sets the background color of the control
///
@@ -43,7 +57,23 @@ public override Color BackColor
}
}
- protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
+ public Color DisabledBackColor { get; set; } = Color.Gray;
+ public Color DisabledForeColor { get; set; } = Color.Black;
+ public Color EnabledBackColor { get; set; } = Color.White;
+ public Color EnabledForeColor { get; set; } = Color.Black;
+
+ protected override CreateParams CreateParams
+ {
+ get
+ {
+ CreateParams handleParam = base.CreateParams;
+ //prevent flickering of the control
+ handleParam.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
+ return handleParam;
+ }
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
@@ -57,7 +87,7 @@ protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
ComboBoxState visual_state;
- // When enabled the brush is set to Backcolor,
+ // When enabled the brush is set to Backcolor,
// otherwise to color stored in _disabled_back_Color
if (this.Enabled)
{
@@ -75,8 +105,8 @@ protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
// Filling the background
g.FillRectangle(bb, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
- // Drawing the datetime text
- g.DrawString(this.Text, this.Font, fb, 5, 2);
+ // Drawing the datetime
+ DrawDateTime(g);
// Drawing the dropdownbutton using ComboBoxRenderer
ComboBoxRenderer.DrawDropDownButton(g, ddb_rect, visual_state);
@@ -85,4 +115,234 @@ protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
bb.Dispose();
fb.Dispose();
}
+
+ ///
+ /// this draw the datetime text manually so we can save the information where day, month and year is
+ ///
+ ///
+ private void DrawDateTime(Graphics g)
+ {
+ // Drawing the datetime text
+ string format;
+ DateTimeFormatInfo dtfi = (new CultureInfo("hr-HR")).DateTimeFormat;
+ switch (Format)
+ {
+ case DateTimePickerFormat.Long:
+ format = dtfi.LongDatePattern;
+ break;
+
+ case DateTimePickerFormat.Short:
+ format = dtfi.ShortDatePattern;
+ break;
+
+ case DateTimePickerFormat.Time:
+ format = dtfi.LongTimePattern;
+ break;
+
+ default:
+ format = CustomFormat;
+ break;
+ }
+ //recalculate the positions of the DateTime parts within the DateTimePicker
+ RecalcPartPositions(format);
+ //draw the DateTime parts
+ var brush = new SolidBrush(this.ForeColor);
+ var highlightBrush = new SolidBrush(this.ForeColor.Highlight());
+ foreach (var part in _dateParts)
+ {
+ if (part.Selected)
+ g.DrawString(this.Value.ToString(part.Format), this.Font, highlightBrush, part.Start, 2);
+ else
+ g.DrawString(this.Value.ToString(part.Format), this.Font, brush, part.Start, 2);
+ }
+ }
+
+ ///
+ /// increment/decrement the selected part
+ ///
+ ///
+ private void IncrementDecrement(Keys key)
+ {
+ if (key == Keys.Up)
+ UpdatePart(1);
+ else if (key == Keys.Down)
+ UpdatePart(-1);
+ }
+
+ ///
+ /// navigate through the DateTime parts with the arrow keys
+ ///
+ ///
+ ///
+ private void NavigateParts(Keys keyCode)
+ {
+ var part = _dateParts.FirstOrDefault(p => p.Selected);
+ if (part == null && keyCode == Keys.Left)
+ {
+ _dateParts.Last().Selected = true;
+ }
+ else if (part != null && keyCode == Keys.Left)
+ {
+ part.Selected = false;
+ var index = _dateParts.IndexOf(part);
+ if (index == 0)
+ _dateParts.Last().Selected = true;
+ else
+ _dateParts[index - 1].Selected = true;
+ }
+ else if (part == null && keyCode == Keys.Right)
+ {
+ _dateParts.First().Selected = true;
+ }
+ else if (part != null && keyCode == Keys.Right)
+ {
+ part.Selected = false;
+ var index = _dateParts.IndexOf(part);
+ if (index == _dateParts.Count - 1)
+ _dateParts.First().Selected = true;
+ else
+ _dateParts[index + 1].Selected = true;
+ }
+ }
+
+ ///
+ /// recalculate the positions of the DateTime parts
+ ///
+ ///
+ private void RecalcPartPositions(string format)
+ {
+ if (_oldFormat != format || _oldWidth != this.Width)
+ {
+ //reset old list
+ _dateParts.Clear();
+
+ //split the format into parts
+ var parts = Regex.Split(format, @"(?<=[^0-9a-zA-Z]+)");
+ int startPos = 5;
+ foreach (var part in parts)
+ {
+ if (String.IsNullOrWhiteSpace(part)) continue;
+ //get the size of the part
+ var size = TextRenderer.MeasureText(this.Value.ToString(part), this.Font);
+ //add the part to the list
+ _dateParts.Add(new DatePartInfo()
+ {
+ Format = part,
+ Start = startPos,
+ End = startPos + size.Width + PART_SPACE
+ });
+ startPos += size.Width + PART_SPACE;
+ }
+
+ //store the format and width so we do not have to recalculate the positions if nothing changed
+ _oldFormat = format;
+ _oldWidth = this.Width;
+ }
+ }
+
+ ///
+ /// allow navigating through the DateTime parts with the arrow keys and increment/decrement the selected part or
+ /// specify the value with the number keys
+ ///
+ ///
+ ///
+ ///
+ private void StylableDateTimePicker_KeyDown(object? sender, KeyEventArgs e)
+ {
+ if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
+ {
+ IncrementDecrement(e.KeyCode);
+ }
+ else if (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
+ {
+ NavigateParts(e.KeyCode);
+ }
+ }
+
+ ///
+ /// reset select part of the format when the control lost focus
+ ///
+ ///
+ ///
+ ///
+ private void StylableDateTimePicker_Leave(object? sender, EventArgs e)
+ {
+ _dateParts.ForEach(p => p.Selected = false);
+ this.Invalidate();
+ }
+
+ ///
+ /// support selecting the DateTime parts with the mouse to change them
+ ///
+ ///
+ ///
+ ///
+ private void StylableDateTimePicker_MouseDown(object? sender, MouseEventArgs e)
+ {
+ _dateParts.ForEach(p => p.Selected = false);
+ var part = _dateParts.FirstOrDefault(p => p.Start <= e.X && p.End >= e.X);
+ if (part != null)
+ {
+ part.Selected = true;
+ this.Invalidate();
+ }
+ }
+
+ ///
+ /// Allow changing the current value with the mouse wheel
+ ///
+ ///
+ ///
+ ///
+ private void StylableDateTimePicker_MouseWheel(object? sender, MouseEventArgs e)
+ {
+ UpdatePart(e.Delta / 100);
+ }
+
+ ///
+ /// update the selected part of the DateTime after user change
+ ///
+ ///
+ private void UpdatePart(int num = 1)
+ {
+ var part = _dateParts.FirstOrDefault(p => p.Selected);
+ if (num != 0 && part != null)
+ {
+ //create a timespan with the given number of days, months, years, hours, minutes or seconds
+ int absNumber = Math.Abs(num);
+ DateTime tmp = new(absNumber, absNumber, absNumber, absNumber, absNumber, absNumber);
+ var tsFmt = Regex.Replace(part.Format, @"([^a-zA-Z0-9]+)", "\\$1").ToLower();
+ TimeSpan ts = TimeSpan.ParseExact(tmp.ToString(part.Format, CultureInfo.CurrentCulture), tsFmt, CultureInfo.CurrentCulture);
+ if (num > 0)
+ this.Value = this.Value.Add(ts);
+ else
+ this.Value = this.Value.Subtract(ts);
+ }
+ }
+
+ ///
+ /// contains the information about the DateTime part and its position
+ ///
+ private class DatePartInfo
+ {
+ ///
+ /// the end X position of the DateTime part
+ ///
+ public int End { get; set; }
+
+ ///
+ /// the format of the DateTime part
+ ///
+ public string Format { get; set; }
+
+ ///
+ /// if true, this part is selected and can be changed by the user
+ ///
+ public bool Selected { get; set; }
+
+ ///
+ /// the start X position of the DateTime part
+ ///
+ public int Start { get; set; }
+ }
}
\ No newline at end of file
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Extensions/ColorExtensions.cs b/StylableWinFormsControls/StylableWinFormsControls/Extensions/ColorExtensions.cs
new file mode 100644
index 0000000..48dcdfe
--- /dev/null
+++ b/StylableWinFormsControls/StylableWinFormsControls/Extensions/ColorExtensions.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StylableWinFormsControls.Extensions
+{
+ ///
+ /// Extensions for interaction with colors
+ ///
+ internal static class ColorExtensions
+ {
+ ///
+ /// the middle value of the rgb range between light and dark:
+ /// FF + FF + FF = 2FD
+ /// 2FD /2 = 17E
+ /// 17E => 382
+ ///
+ public const int RGB_MIDDLE_VALUE = 382;
+ ///
+ /// makes a color lighter
+ ///
+ ///
+ ///
+ internal static Color Lighter(this Color c)
+ {
+ return ControlPaint.Light(c, 1f);
+ }
+ ///
+ /// makes a color darker
+ ///
+ ///
+ ///
+ internal static Color Darker(this Color c)
+ {
+ return ControlPaint.Dark(c, 1f);
+ }
+ ///
+ /// highlights a color
+ ///
+ ///
+ ///
+ internal static Color Highlight(this Color c)
+ {
+ //ignore alpha value in checking if we need to make it darker or lighter
+ int rgb = c.R + c.G + c.B;
+ if(rgb > RGB_MIDDLE_VALUE)
+ {
+ return c.Darker();
+ }
+ else
+ {
+ return c.Lighter();
+ }
+ }
+ }
+}
From 6b4775dac832043e3e262c172ec454ac21859c2f Mon Sep 17 00:00:00 2001
From: "13997737+wolframhaussig@users.noreply.github.com"
<13997737+wolframhaussig@users.noreply.github.com>
Date: Wed, 9 Aug 2023 21:28:24 +0200
Subject: [PATCH 2/5] fix Up/Down-Buttons seem to increment/decrement twice
---
.../Controls/StylableDateTimePicker.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index 3be3187..8eca2df 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -156,7 +156,7 @@ private void DrawDateTime(Graphics g)
g.DrawString(this.Value.ToString(part.Format), this.Font, brush, part.Start, 2);
}
}
-
+ int counter = 0;
///
/// increment/decrement the selected part
///
@@ -257,6 +257,7 @@ private void StylableDateTimePicker_KeyDown(object? sender, KeyEventArgs e)
{
NavigateParts(e.KeyCode);
}
+ e.Handled = true;
}
///
@@ -313,6 +314,7 @@ private void UpdatePart(int num = 1)
DateTime tmp = new(absNumber, absNumber, absNumber, absNumber, absNumber, absNumber);
var tsFmt = Regex.Replace(part.Format, @"([^a-zA-Z0-9]+)", "\\$1").ToLower();
TimeSpan ts = TimeSpan.ParseExact(tmp.ToString(part.Format, CultureInfo.CurrentCulture), tsFmt, CultureInfo.CurrentCulture);
+ counter += num;
if (num > 0)
this.Value = this.Value.Add(ts);
else
From bc9f5918d736a0838a9476e0cb461ca2244d45d5 Mon Sep 17 00:00:00 2001
From: "13997737+wolframhaussig@users.noreply.github.com"
<13997737+wolframhaussig@users.noreply.github.com>
Date: Wed, 9 Aug 2023 21:44:12 +0200
Subject: [PATCH 3/5] support dates
---
.../Controls/StylableDateTimePicker.cs | 63 ++++++++++++++++---
1 file changed, 54 insertions(+), 9 deletions(-)
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index 8eca2df..5f1a736 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -256,6 +256,7 @@ private void StylableDateTimePicker_KeyDown(object? sender, KeyEventArgs e)
else if (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
{
NavigateParts(e.KeyCode);
+ this.Invalidate();
}
e.Handled = true;
}
@@ -309,17 +310,61 @@ private void UpdatePart(int num = 1)
var part = _dateParts.FirstOrDefault(p => p.Selected);
if (num != 0 && part != null)
{
- //create a timespan with the given number of days, months, years, hours, minutes or seconds
- int absNumber = Math.Abs(num);
- DateTime tmp = new(absNumber, absNumber, absNumber, absNumber, absNumber, absNumber);
- var tsFmt = Regex.Replace(part.Format, @"([^a-zA-Z0-9]+)", "\\$1").ToLower();
- TimeSpan ts = TimeSpan.ParseExact(tmp.ToString(part.Format, CultureInfo.CurrentCulture), tsFmt, CultureInfo.CurrentCulture);
- counter += num;
- if (num > 0)
- this.Value = this.Value.Add(ts);
+ if (part.Format.StartsWith("M"))
+ {
+ UpdatePartMonth(num);
+ }
+ else if (part.Format.StartsWith("y"))
+ {
+ UpdatePartYear(num);
+ }
else
- this.Value = this.Value.Subtract(ts);
+ {
+ UpdatePartGeneric(num, part);
+ }
+ this.Invalidate();
+ }
+ }
+ ///
+ /// increment or decrement the month part of the DateTime
+ ///
+ ///
+ private void UpdatePartMonth(int num)
+ {
+ this.Value = this.Value.AddMonths(num);
+ }
+ ///
+ /// increment or decrement the year part of the DateTime
+ ///
+ ///
+ private void UpdatePartYear(int num)
+ {
+ this.Value = this.Value.AddYears(num);
+ }
+ ///
+ /// generically increment or decrement the selected part of the DateTime. Does not work for months and years
+ ///
+ ///
+ ///
+ private void UpdatePartGeneric(int num, DatePartInfo part)
+ {
+ //create a timespan with the given number of days, hours, minutes or seconds
+ int absNumber = Math.Abs(num);
+ DateTime tmp = new(absNumber, absNumber, absNumber, absNumber, absNumber, absNumber);
+ var dFmt = part.Format;
+ var tsFmt = Regex.Replace(dFmt, @"([^a-zA-Z0-9]+)", "\\$1").ToLower();
+ //special handling for fullname of days as we want to get a numeric value to add or subtract
+ if (dFmt.StartsWith("d") && dFmt.Length > 3)
+ {
+ dFmt = "dd";
+ tsFmt = "dd";
}
+ TimeSpan ts = TimeSpan.ParseExact(tmp.ToString(dFmt, CultureInfo.CurrentCulture), tsFmt, CultureInfo.CurrentCulture);
+ counter += num;
+ if (num > 0)
+ this.Value = this.Value.Add(ts);
+ else
+ this.Value = this.Value.Subtract(ts);
}
///
From 1753730a5d4757e15249465e465d538966e63d95 Mon Sep 17 00:00:00 2001
From: "13997737+wolframhaussig@users.noreply.github.com"
<13997737+wolframhaussig@users.noreply.github.com>
Date: Wed, 9 Aug 2023 21:49:07 +0200
Subject: [PATCH 4/5] fix text overlapping each other for longer names in dates
---
.../Controls/StylableDateTimePicker.cs | 43 ++++++++++---------
1 file changed, 23 insertions(+), 20 deletions(-)
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index 5f1a736..807a04c 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -150,13 +150,14 @@ private void DrawDateTime(Graphics g)
var highlightBrush = new SolidBrush(this.ForeColor.Highlight());
foreach (var part in _dateParts)
{
+ Rectangle bounds = new Rectangle(part.Start, 2, part.End - part.Start, this.Height - 2);
if (part.Selected)
- g.DrawString(this.Value.ToString(part.Format), this.Font, highlightBrush, part.Start, 2);
+ TextRenderer.DrawText(g, this.Value.ToString(part.Format), Font, bounds, this.ForeColor.Highlight(), TextFormatFlags.EndEllipsis);
else
- g.DrawString(this.Value.ToString(part.Format), this.Font, brush, part.Start, 2);
+ TextRenderer.DrawText(g, this.Value.ToString(part.Format), Font, bounds, this.ForeColor, TextFormatFlags.EndEllipsis);
}
}
- int counter = 0;
+
///
/// increment/decrement the selected part
///
@@ -325,22 +326,7 @@ private void UpdatePart(int num = 1)
this.Invalidate();
}
}
- ///
- /// increment or decrement the month part of the DateTime
- ///
- ///
- private void UpdatePartMonth(int num)
- {
- this.Value = this.Value.AddMonths(num);
- }
- ///
- /// increment or decrement the year part of the DateTime
- ///
- ///
- private void UpdatePartYear(int num)
- {
- this.Value = this.Value.AddYears(num);
- }
+
///
/// generically increment or decrement the selected part of the DateTime. Does not work for months and years
///
@@ -360,13 +346,30 @@ private void UpdatePartGeneric(int num, DatePartInfo part)
tsFmt = "dd";
}
TimeSpan ts = TimeSpan.ParseExact(tmp.ToString(dFmt, CultureInfo.CurrentCulture), tsFmt, CultureInfo.CurrentCulture);
- counter += num;
if (num > 0)
this.Value = this.Value.Add(ts);
else
this.Value = this.Value.Subtract(ts);
}
+ ///
+ /// increment or decrement the month part of the DateTime
+ ///
+ ///
+ private void UpdatePartMonth(int num)
+ {
+ this.Value = this.Value.AddMonths(num);
+ }
+
+ ///
+ /// increment or decrement the year part of the DateTime
+ ///
+ ///
+ private void UpdatePartYear(int num)
+ {
+ this.Value = this.Value.AddYears(num);
+ }
+
///
/// contains the information about the DateTime part and its position
///
From 4b3bd2d0687b2a6f19895387e05b63b41a2959ce Mon Sep 17 00:00:00 2001
From: "13997737+wolframhaussig@users.noreply.github.com"
<13997737+wolframhaussig@users.noreply.github.com>
Date: Thu, 10 Aug 2023 18:35:38 +0200
Subject: [PATCH 5/5] Support entering the numeric values for
StylableDateTimePicker (e.g. not full day of week or month names)
---
.../Controls/StylableDateTimePicker.cs | 137 +++++++++++++-----
1 file changed, 100 insertions(+), 37 deletions(-)
diff --git a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index 807a04c..a58e8b6 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -20,7 +20,7 @@ public class StylableDateTimePicker : DateTimePicker
///
/// contains all DateTime parts and their positions
///
- private readonly List _dateParts = new List();
+ private readonly List _dateParts = new();
///
/// when the format is changed we need to recalculate the positions of the parts
@@ -39,6 +39,7 @@ public StylableDateTimePicker()
this.Leave += StylableDateTimePicker_Leave;
this.MouseWheel += StylableDateTimePicker_MouseWheel;
this.KeyDown += StylableDateTimePicker_KeyDown;
+ this.KeyPress += StylableDateTimePicker_KeyPress;
}
///
@@ -116,6 +117,31 @@ protected override void OnPaint(PaintEventArgs e)
fb.Dispose();
}
+ ///
+ /// replace a part of the date with the manually entered input
+ ///
+ /// the old date
+ /// the full format
+ /// the part format
+ /// the new part value
+ ///
+ private static DateTime Replace(DateTime date, string format, string partFormat, string value)
+ {
+ //remove the day of week from the format as DateTime.ParseExact throws if date and day of week do not match
+ format = format.Replace("dddd", "");
+ format = format.Replace("ddd", "");
+ //remove the ful month name as this is not numeric and we do not support it currently
+ format = format.Replace("MMMM", "");
+ format = format.Replace("MMM", "");
+ //convert the old date to a string where the part to replace is XXXX
+ string partFormatRegex = @"\b" + Regex.Replace(partFormat, "[^a-zA-Z0-9]+", "") + @"\b";
+ string formattedDate = date.ToString(Regex.Replace(format, partFormatRegex, @"\X\X\X\X"));
+ //now we can simply replace the XXXX with the new value and reparse the date
+ string replacedDateString = formattedDate.Replace("XXXX", value);
+ var replacedDate = DateTime.ParseExact(replacedDateString, format, CultureInfo.CurrentCulture);
+ return replacedDate;
+ }
+
///
/// this draw the datetime text manually so we can save the information where day, month and year is
///
@@ -123,26 +149,7 @@ protected override void OnPaint(PaintEventArgs e)
private void DrawDateTime(Graphics g)
{
// Drawing the datetime text
- string format;
- DateTimeFormatInfo dtfi = (new CultureInfo("hr-HR")).DateTimeFormat;
- switch (Format)
- {
- case DateTimePickerFormat.Long:
- format = dtfi.LongDatePattern;
- break;
-
- case DateTimePickerFormat.Short:
- format = dtfi.ShortDatePattern;
- break;
-
- case DateTimePickerFormat.Time:
- format = dtfi.LongTimePattern;
- break;
-
- default:
- format = CustomFormat;
- break;
- }
+ string format = GetFormat();
//recalculate the positions of the DateTime parts within the DateTimePicker
RecalcPartPositions(format);
//draw the DateTime parts
@@ -150,7 +157,7 @@ private void DrawDateTime(Graphics g)
var highlightBrush = new SolidBrush(this.ForeColor.Highlight());
foreach (var part in _dateParts)
{
- Rectangle bounds = new Rectangle(part.Start, 2, part.End - part.Start, this.Height - 2);
+ Rectangle bounds = new(part.Start, 2, part.End - part.Start, this.Height - 2);
if (part.Selected)
TextRenderer.DrawText(g, this.Value.ToString(part.Format), Font, bounds, this.ForeColor.Highlight(), TextFormatFlags.EndEllipsis);
else
@@ -158,6 +165,19 @@ private void DrawDateTime(Graphics g)
}
}
+ private string GetFormat()
+ {
+ DateTimeFormatInfo dtfi = (CultureInfo.CurrentCulture).DateTimeFormat;
+ string format = Format switch
+ {
+ DateTimePickerFormat.Long => dtfi.LongDatePattern,
+ DateTimePickerFormat.Short => dtfi.ShortDatePattern,
+ DateTimePickerFormat.Time => dtfi.LongTimePattern,
+ _ => CustomFormat,
+ };
+ return format;
+ }
+
///
/// increment/decrement the selected part
///
@@ -170,11 +190,28 @@ private void IncrementDecrement(Keys key)
UpdatePart(-1);
}
+ ///
+ /// move the selection to the next part
+ ///
+ ///
+ /// the newly selected part
+ private DatePartInfo NavigatePartRight(DatePartInfo part)
+ {
+ part.Reset();
+ var index = _dateParts.IndexOf(part);
+ DatePartInfo newPart;
+ if (index == _dateParts.Count - 1)
+ newPart = _dateParts.First();
+ else
+ newPart = _dateParts[index + 1];
+ newPart.Selected = true;
+ return newPart;
+ }
+
///
/// navigate through the DateTime parts with the arrow keys
///
///
- ///
private void NavigateParts(Keys keyCode)
{
var part = _dateParts.FirstOrDefault(p => p.Selected);
@@ -184,7 +221,7 @@ private void NavigateParts(Keys keyCode)
}
else if (part != null && keyCode == Keys.Left)
{
- part.Selected = false;
+ part.Reset();
var index = _dateParts.IndexOf(part);
if (index == 0)
_dateParts.Last().Selected = true;
@@ -197,12 +234,7 @@ private void NavigateParts(Keys keyCode)
}
else if (part != null && keyCode == Keys.Right)
{
- part.Selected = false;
- var index = _dateParts.IndexOf(part);
- if (index == _dateParts.Count - 1)
- _dateParts.First().Selected = true;
- else
- _dateParts[index + 1].Selected = true;
+ NavigatePartRight(part);
}
}
@@ -247,7 +279,6 @@ private void RecalcPartPositions(string format)
///
///
///
- ///
private void StylableDateTimePicker_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
@@ -262,15 +293,38 @@ private void StylableDateTimePicker_KeyDown(object? sender, KeyEventArgs e)
e.Handled = true;
}
+ ///
+ /// allow directly editing a value with the number keys
+ ///
+ ///
+ ///
+ private void StylableDateTimePicker_KeyPress(object? sender, KeyPressEventArgs e)
+ {
+ if (Char.IsNumber(e.KeyChar))
+ {
+ var part = _dateParts.FirstOrDefault(p => p.Selected);
+ if (part != null)
+ {
+ part.NewValue += e.KeyChar;
+ if (part.NewValue.Length >= part.Format.Length)
+ {
+ this.Value = Replace(this.Value, GetFormat(), part.Format, part.NewValue);
+ part = NavigatePartRight(part);
+ }
+ this.Invalidate();
+ }
+ }
+ e.Handled = true;
+ }
+
///
/// reset select part of the format when the control lost focus
///
///
///
- ///
private void StylableDateTimePicker_Leave(object? sender, EventArgs e)
{
- _dateParts.ForEach(p => p.Selected = false);
+ _dateParts.ForEach(p => p.Reset());
this.Invalidate();
}
@@ -279,10 +333,9 @@ private void StylableDateTimePicker_Leave(object? sender, EventArgs e)
///
///
///
- ///
private void StylableDateTimePicker_MouseDown(object? sender, MouseEventArgs e)
{
- _dateParts.ForEach(p => p.Selected = false);
+ _dateParts.ForEach(p => p.Reset());
var part = _dateParts.FirstOrDefault(p => p.Start <= e.X && p.End >= e.X);
if (part != null)
{
@@ -296,7 +349,6 @@ private void StylableDateTimePicker_MouseDown(object? sender, MouseEventArgs e)
///
///
///
- ///
private void StylableDateTimePicker_MouseWheel(object? sender, MouseEventArgs e)
{
UpdatePart(e.Delta / 100);
@@ -383,7 +435,9 @@ private class DatePartInfo
///
/// the format of the DateTime part
///
- public string Format { get; set; }
+ public string Format { get; set; } = string.Empty;
+
+ public string NewValue { get; set; } = string.Empty;
///
/// if true, this part is selected and can be changed by the user
@@ -394,5 +448,14 @@ private class DatePartInfo
/// the start X position of the DateTime part
///
public int Start { get; set; }
+
+ ///
+ ///
+ ///
+ public void Reset()
+ {
+ NewValue = string.Empty;
+ Selected = false;
+ }
}
}
\ No newline at end of file