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/Controls/StylableDateTimePicker.cs b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
index 28d29fc..a58e8b6 100644
--- a/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
+++ b/StylableWinFormsControls/StylableWinFormsControls/Controls/StylableDateTimePicker.cs
@@ -1,5 +1,8 @@
+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,77 +12,450 @@ 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();
+
+ ///
+ /// 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()
{
- SetStyle(ControlStyles.UserPaint, true);
+ this.SetStyle(ControlStyles.UserPaint, true);
+ this.MouseDown += StylableDateTimePicker_MouseDown;
+ this.Leave += StylableDateTimePicker_Leave;
+ this.MouseWheel += StylableDateTimePicker_MouseWheel;
+ this.KeyDown += StylableDateTimePicker_KeyDown;
+ this.KeyPress += StylableDateTimePicker_KeyPress;
}
-
+
+ ///
+ /// Gets or sets the background color of the control
+ ///
+ [Browsable(true)]
+ public override Color BackColor
+ {
+ get
+ {
+ return base.BackColor;
+ }
+ set
+ {
+ base.BackColor = value;
+ }
+ }
+
+ 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
+ handleParam.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
return handleParam;
}
- }
-
- ///
- /// Gets or sets the background color of the control
- ///
- [Browsable(true)]
- public override Color BackColor
- {
- get => base.BackColor;
- set => base.BackColor = value;
}
protected override void OnPaint(PaintEventArgs e)
{
- Graphics g = CreateGraphics();
+ Graphics g = this.CreateGraphics();
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
// Dropdownbutton rectangle
- Rectangle ddbRect = new(ClientRectangle.Width - 17, 0, 17, ClientRectangle.Height);
+ Rectangle ddb_rect = new Rectangle(ClientRectangle.Width - 17, 0, 17, ClientRectangle.Height);
// Background brush
Brush bb;
//foreground brush
Brush fb;
- ComboBoxState visualState;
+ 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 (Enabled)
+ if (this.Enabled)
{
bb = new SolidBrush(EnabledBackColor);
fb = new SolidBrush(EnabledForeColor);
- visualState = ComboBoxState.Normal;
+ visual_state = ComboBoxState.Normal;
}
else
{
bb = new SolidBrush(DisabledBackColor);
fb = new SolidBrush(DisabledForeColor);
- visualState = ComboBoxState.Disabled;
+ visual_state = ComboBoxState.Disabled;
}
// Filling the background
g.FillRectangle(bb, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
- // Drawing the datetime text
- g.DrawString(Text, Font, fb, 5, 2);
+ // Drawing the datetime
+ DrawDateTime(g);
// Drawing the dropdownbutton using ComboBoxRenderer
- ComboBoxRenderer.DrawDropDownButton(g, ddbRect, visualState);
+ ComboBoxRenderer.DrawDropDownButton(g, ddb_rect, visual_state);
g.Dispose();
bb.Dispose();
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
+ ///
+ ///
+ private void DrawDateTime(Graphics g)
+ {
+ // Drawing the datetime text
+ string format = GetFormat();
+ //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)
+ {
+ 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
+ TextRenderer.DrawText(g, this.Value.ToString(part.Format), Font, bounds, this.ForeColor, TextFormatFlags.EndEllipsis);
+ }
+ }
+
+ 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
+ ///
+ ///
+ private void IncrementDecrement(Keys key)
+ {
+ if (key == Keys.Up)
+ UpdatePart(1);
+ else if (key == Keys.Down)
+ 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);
+ if (part == null && keyCode == Keys.Left)
+ {
+ _dateParts.Last().Selected = true;
+ }
+ else if (part != null && keyCode == Keys.Left)
+ {
+ part.Reset();
+ 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)
+ {
+ NavigatePartRight(part);
+ }
+ }
+
+ ///
+ /// 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);
+ this.Invalidate();
+ }
+ 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.Reset());
+ 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.Reset());
+ 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)
+ {
+ if (part.Format.StartsWith("M"))
+ {
+ UpdatePartMonth(num);
+ }
+ else if (part.Format.StartsWith("y"))
+ {
+ UpdatePartYear(num);
+ }
+ else
+ {
+ UpdatePartGeneric(num, part);
+ }
+ this.Invalidate();
+ }
+ }
+
+ ///
+ /// 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);
+ 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
+ ///
+ 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; } = string.Empty;
+
+ public string NewValue { get; set; } = string.Empty;
+
+ ///
+ /// 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; }
+
+ ///
+ ///
+ ///
+ public void Reset()
+ {
+ NewValue = string.Empty;
+ Selected = false;
+ }
+ }
+}
\ 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();
+ }
+ }
+ }
+}