Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-testform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
${{ runner.os }}-nuget-

- name: Restore TestForm
run: dotnet restore "Source/Krypton Components/TestForm/TestForm.csproj" -p:Configuration=Release -p:TFMs=all
run: dotnet restore "Source/Krypton Components/TestForm/TestForm.csproj" -p:Configuration=Release -p:TFMs=all -p:ExcludeNet11=true

- name: Validate TestForm linked resources
shell: pwsh
Expand Down Expand Up @@ -137,7 +137,7 @@ jobs:
}

- name: Build TestForm
run: dotnet build "Source/Krypton Components/TestForm/TestForm.csproj" -p:Configuration=Release --no-restore -p:TFMs=all -m:1 -p:BuildInParallel=false
run: dotnet build "Source/Krypton Components/TestForm/TestForm.csproj" -p:Configuration=Release --no-restore -p:TFMs=all -p:ExcludeNet11=true -m:1 -p:BuildInParallel=false

- name: Save NuGet cache
if: success() && github.event_name != 'pull_request'
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Direct VS2026 presets: `.\Scripts\Current\build-stable.cmd`, `.\Scripts\Current\build-canary.cmd`, `.\Scripts\Current\build-nightly.cmd`.
- Outputs land under `Bin\<Configuration>\<TargetFramework>\` by default; with `UseArtifactsOutput=true`, outputs land under `artifacts\bin\<Configuration>\<TargetFramework>\`.
- Target frameworks are selected by MSBuild properties. VS2019/full MSBuild builds only .NET Framework 4.x TFMs; VS2022/full MSBuild excludes `net10.0-windows` and `net11.0-windows`; VS2026/full MSBuild excludes `net11.0-windows` unless explicitly enabled; CI or SDK-based builds can include `net472`, `net48`, `net481`, `net8.0-windows`, `net9.0-windows`, `net10.0-windows`, and `net11.0-windows` when the required SDKs are installed.
- New files must use only the current Standard Toolkit BSD header. Do not add the original ComponentFactory BSD header unless the file is derived from original ComponentFactory source.

## Coding Style & Naming Conventions

Expand Down
1 change: 1 addition & 0 deletions Documents/Changelog/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

## 2026-11-xx - Build 2611 (V110 Nightly) - November 2026

* Resolved [#3736](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3736), Fixed high-dpi scaling causing magenta image borders
* Implemented [#3718](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3718), Use 'switch' expression
* Resolved [#3720](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3720), `KryptonTreeView` freezes blank after programmatic `SelectedNode = null` then re-select (drain leaked selection `BeginUpdate`/`EndUpdate` batches from #3282/#3498)
* Resolved [#3719](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3719), Information exposure through transmitted data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private class ToolTipContent : IContentValues
private KryptonVScrollBar? _roundingVScrollBar;
private KryptonHScrollBar? _roundingHScrollBar;
private bool _suppressRoundingScrollSync;
private bool _roundingResizeScrollSyncPending;
private int _roundingScrollBarInteractionDepth;
private System.Windows.Forms.Timer? _roundingScrollSyncTimer;
private string _toolTipText;
Expand Down Expand Up @@ -1155,10 +1156,7 @@ protected override void OnScroll(ScrollEventArgs e)
{
base.OnScroll(e);

if (_roundingUsesDetachedScrollbars && !_suppressRoundingScrollSync && _roundingScrollBarInteractionDepth == 0)
{
SyncDetachedRoundingScrollbarsFromGrid(false);
}
SyncDetachedRoundingScrollbarsFromGridIfIdle(false);

// #2681 - work-around
// Headers not correctly repainted on horizontal mouse scroll
Expand Down Expand Up @@ -1333,7 +1331,7 @@ or KryptonDataGridViewDomainUpDownCell
// the painting to fail.
if ((_cellDown.X == -1) || (_cellDown.Y == -1))
{
DoubleBuffered = false;
base.DoubleBuffered = false;
}

base.OnCellMouseDown(e);
Expand All @@ -1358,9 +1356,9 @@ protected override void OnCellMouseUp(DataGridViewCellMouseEventArgs e)
_cellDown = _nullCell;

// Put back double buffered if it was turned off in the OnCellMouseDown
if (!DoubleBuffered)
if (!base.DoubleBuffered)
{
DoubleBuffered = true;
base.DoubleBuffered = true;
}

base.OnCellMouseUp(e);
Expand Down Expand Up @@ -1876,6 +1874,7 @@ protected override void OnResize(EventArgs e)
{
base.OnResize(e);
UpdateRoundingAppearance();
QueueDetachedRoundingScrollbarsResizeSync();
}

/// <summary>
Expand Down Expand Up @@ -2799,6 +2798,7 @@ private void DisableDetachedRoundingScrollbars()

bool wasUsingDetachedScrollbars = _roundingUsesDetachedScrollbars;
_roundingScrollBarInteractionDepth = 0;
_roundingResizeScrollSyncPending = false;
if (wasUsingDetachedScrollbars)
{
ShowNativeScrollbarsAfterRounding();
Expand Down Expand Up @@ -2981,24 +2981,18 @@ private void SyncDetachedRoundingScrollbarsFromGrid(bool layoutScrollbars)
return;
}

var hScrollInfo = new WIN32ScrollBars.ScrollInfo
{
cbSize = Marshal.SizeOf(typeof(WIN32ScrollBars.ScrollInfo)),
fMask = (int)PI.SIF_.ALL
};
var vScrollInfo = new WIN32ScrollBars.ScrollInfo
{
cbSize = Marshal.SizeOf(typeof(WIN32ScrollBars.ScrollInfo)),
fMask = (int)PI.SIF_.ALL
};

bool hasHScroll = PI.GetScrollInfo(Handle, PI.SB_.HORZ, ref hScrollInfo);
bool hasVScroll = PI.GetScrollInfo(Handle, PI.SB_.VERT, ref vScrollInfo);

_suppressRoundingScrollSync = true;
try
{
UpdateDetachedHorizontalScrollBar(hasHScroll, hScrollInfo);
UpdateDetachedHorizontalScrollBar();
UpdateDetachedVerticalScrollBar(hasVScroll, vScrollInfo);
}
finally
Expand All @@ -3009,41 +3003,66 @@ private void SyncDetachedRoundingScrollbarsFromGrid(bool layoutScrollbars)
LayoutDetachedRoundingScrollbars(layoutScrollbars);
}

private void UpdateDetachedHorizontalScrollBar(bool hasNativeScrollInfo, WIN32ScrollBars.ScrollInfo scrollInfo)
private void SyncDetachedRoundingScrollbarsFromGridIfIdle(bool layoutScrollbars)
{
if (_roundingUsesDetachedScrollbars && !_suppressRoundingScrollSync && _roundingScrollBarInteractionDepth == 0)
{
SyncDetachedRoundingScrollbarsFromGrid(layoutScrollbars);
}
}

private void QueueDetachedRoundingScrollbarsResizeSync()
{
if (!_roundingUsesDetachedScrollbars
|| _roundingResizeScrollSyncPending
|| !IsHandleCreated
|| IsDisposed
|| Disposing)
{
return;
}

_roundingResizeScrollSyncPending = true;
BeginInvoke((System.Windows.Forms.MethodInvoker)(() =>
{
_roundingResizeScrollSyncPending = false;

if (!_roundingUsesDetachedScrollbars
|| !IsHandleCreated
|| IsDisposed
|| Disposing)
{
return;
}

HideNativeScrollbarsForRounding();
SyncDetachedRoundingScrollbarsFromGridIfIdle(true);
}));
}

private void UpdateDetachedHorizontalScrollBar()
{
if (_roundingHScrollBar == null || !WantsDetachedHorizontalScrollBar())
{
if (_roundingHScrollBar != null)
if (_roundingHScrollBar != null && _roundingHScrollBar.Visible)
{
_roundingHScrollBar.Visible = false;
}

return;
}

SetDetachedHorizontalScrollBarBounds(false);

int minimum;
int maximum;
int page;
int position;
if (TryGetNativeDataGridScrollBarMetrics(true, out minimum, out maximum, out page, out position))
{
ApplyDetachedScrollBarMetrics(_roundingHScrollBar, minimum, maximum, page, position);
return;
}

if (hasNativeScrollInfo && TryGetNativeScrollMetrics(scrollInfo, out minimum, out maximum, out page, out position))
if (TryComputeHorizontalScrollMetrics(out minimum, out maximum, out page, out position))
{
ApplyDetachedScrollBarMetrics(_roundingHScrollBar, minimum, maximum, page, position);
return;
}

if (TryComputeHorizontalScrollMetrics(out minimum, out maximum, out page, out position))
{
ApplyDetachedScrollBarMetrics(_roundingHScrollBar, minimum, maximum, page, position);
}
else
if (_roundingHScrollBar.Visible)
{
_roundingHScrollBar.Visible = false;
}
Expand All @@ -3053,16 +3072,14 @@ private void UpdateDetachedVerticalScrollBar(bool hasNativeScrollInfo, WIN32Scro
{
if (_roundingVScrollBar == null || !WantsDetachedVerticalScrollBar())
{
if (_roundingVScrollBar != null)
if (_roundingVScrollBar != null && _roundingVScrollBar.Visible)
{
_roundingVScrollBar.Visible = false;
}

return;
}

SetDetachedVerticalScrollBarBounds(false);

int minimum;
int maximum;
int page;
Expand All @@ -3085,7 +3102,10 @@ private void UpdateDetachedVerticalScrollBar(bool hasNativeScrollInfo, WIN32Scro
}
else
{
_roundingVScrollBar.Visible = false;
if (_roundingVScrollBar.Visible)
{
_roundingVScrollBar.Visible = false;
}
}
}

Expand Down Expand Up @@ -3339,33 +3359,7 @@ private int GetVerticalScrollPositionInPixels()

private int GetHorizontalScrollPositionInPixels()
{
var hScrollInfo = new WIN32ScrollBars.ScrollInfo
{
cbSize = Marshal.SizeOf(typeof(WIN32ScrollBars.ScrollInfo)),
fMask = (int)PI.SIF_.POS
};

if (IsHandleCreated && PI.GetScrollInfo(Handle, PI.SB_.HORZ, ref hScrollInfo))
{
return hScrollInfo.nPos;
}

int position = 0;
int firstColumn = Math.Max(0, FirstDisplayedScrollingColumnIndex);
if (RowHeadersVisible)
{
position += RowHeadersWidth;
}

for (int i = 0; i < firstColumn && i < Columns.Count; i++)
{
if (Columns[i].Visible)
{
position += Columns[i].Width;
}
}

return position;
return HorizontalScrollingOffset;
}

private static int GetNativeScrollableMaximum(int minimum, int maximum, int page) =>
Expand Down Expand Up @@ -4093,6 +4087,14 @@ protected override void WndProc(ref Message m)
}
#endregion menus

/// <inheritdoc/>
protected override void OnColumnWidthChanged(DataGridViewColumnEventArgs e)
{
base.OnColumnWidthChanged(e);

SyncDetachedRoundingScrollbarsFromGridIfIdle(false);
}

#region Column ButtonSpec wiring
/// <summary>
/// Wire column-specific ButtonSpec click events when a column is added.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5732,9 +5732,11 @@ private static void AllocateImageSpace([DisallowNull] StandardContentMemento mem
float ratio = Math.Min(displayRect.Width / (float)memento.Image.Width,
displayRect.Height / (float)memento.Image.Height);

bool avoidPurple = memento.ImageTransparentColor != GlobalStaticVariables.EMPTY_COLOR;

// Resize image to fit display area
memento.Image = CommonHelper.ScaleImageForSizedDisplay(memento.Image, memento.Image.Width * ratio,
memento.Image.Height * ratio, false);
memento.Image.Height * ratio, avoidPurple);
}

if (memento.Image != null)
Expand Down Expand Up @@ -12395,4 +12397,4 @@ internal void CalculateOverlayImagePosition()
}
}
#endregion
}
}
69 changes: 69 additions & 0 deletions Source/Krypton Components/TestForm/TouchscreenHighDpiDemo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public partial class TouchscreenHighDpiDemo : KryptonForm
{
private Timer _dpiMonitorTimer;
private bool _updatingFromEvent;
private readonly Dictionary<Control, Rectangle> _baselineBounds = new();

public TouchscreenHighDpiDemo()
{
Expand All @@ -48,6 +49,8 @@ private void InitializeForm()
// Setup demo controls
SetupDemoControls();

grpSettings.Panel.AutoScroll = true;

// Setup event handlers
chkEnableTouchscreen.CheckedChanged += ChkEnableTouchscreen_CheckedChanged;
trackScaleFactor.ValueChanged += TrackScaleFactor_ValueChanged;
Expand Down Expand Up @@ -376,13 +379,79 @@ private void UpdateUIFromSettings()
trackFontScaleFactor.Enabled = fontScalingEnabled;
lblFontScaleFactor.Enabled = touchscreenEnabled;
lblFontScaleValue.Enabled = touchscreenEnabled;

ApplyScaledDemoLayout(settings);
}
finally
{
_updatingFromEvent = false;
}
}

private void ApplyScaledDemoLayout(TouchscreenSettingValues settings)
{
CaptureBaselineBounds(grpControls.Panel);
CaptureBaselineBounds(grpSettings.Panel);

float scaleFactor = 1f;
if (settings.TouchscreenModeEnabled)
{
scaleFactor = settings.ControlScaleFactor;
if (settings.FontScalingEnabled)
{
scaleFactor = Math.Max(scaleFactor, settings.FontScaleFactor);
}
}

scaleFactor = Math.Max(1f, scaleFactor);

grpControls.Panel.SuspendLayout();
grpSettings.Panel.SuspendLayout();

try
{
foreach (KeyValuePair<Control, Rectangle> entry in _baselineBounds)
{
Control control = entry.Key;
if (control.IsDisposed || control.Dock == DockStyle.Fill)
{
continue;
}

Rectangle bounds = entry.Value;
control.Bounds = new Rectangle(
ScaleLayoutValue(bounds.X, scaleFactor),
ScaleLayoutValue(bounds.Y, scaleFactor),
ScaleLayoutSize(bounds.Width, scaleFactor),
ScaleLayoutSize(bounds.Height, scaleFactor));
}
}
finally
{
grpSettings.Panel.ResumeLayout(true);
grpControls.Panel.ResumeLayout(true);
}
}

private void CaptureBaselineBounds(Control parent)
{
foreach (Control child in parent.Controls)
{
if (child.Dock != DockStyle.Fill && !_baselineBounds.ContainsKey(child))
{
_baselineBounds.Add(child, child.Bounds);
}

CaptureBaselineBounds(child);
}
}

private static int ScaleLayoutValue(int value, float scaleFactor) =>
Math.Max(0, (int)Math.Round(value * scaleFactor));

private static int ScaleLayoutSize(int value, float scaleFactor) =>
Math.Max(1, (int)Math.Round(value * scaleFactor));

private void UpdateStatus()
{
var settings = KryptonManager.TouchscreenSettingValues;
Expand Down
Loading