Skip to content
Open
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
1 change: 1 addition & 0 deletions src/System.Windows.Forms/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
override System.Windows.Forms.TreeView.RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew) -> void
static System.Windows.Forms.Application.SetColorMode(System.Windows.Forms.SystemColorMode systemColorMode) -> void
static System.Windows.Forms.TaskDialog.ShowDialogAsync(nint hwndOwner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterOwner) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.IWin32Window! owner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterOwner) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ internal ImageList.Indexer SelectedImageIndexer
private Color _lineColor;
private string? _controlToolTipText;

// Scaled ImageList for high DPI support
private ImageList? _scaledImageList;
private int _scaledImageListDpi;

// Sorting
private IComparer? _treeViewNodeSorter;

Expand Down Expand Up @@ -620,6 +624,9 @@ public ImageList? ImageList

_imageList = value;

// Clear the scaled ImageList when the ImageList changes.
ClearScaledImageList();

AttachImageListHandlers();

// Update TreeView's images
Expand Down Expand Up @@ -1553,6 +1560,8 @@ protected override void Dispose(bool disposing)
_imageList = null;
DetachStateImageListHandlers();
_stateImageList = null;

ClearScaledImageList();
}
}

Expand Down Expand Up @@ -1688,6 +1697,9 @@ private void ImageListRecreateHandle(object? sender, EventArgs e)
{
if (IsHandleCreated)
{
// Clear the scaled ImageList so it gets recreated with the new images.
ClearScaledImageList();

IntPtr handle = (ImageList is null) ? IntPtr.Zero : ImageList.Handle;
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, handle);
}
Expand Down Expand Up @@ -1900,7 +1912,19 @@ protected override void OnHandleCreated(EventArgs e)

if (_imageList is not null)
{
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, _imageList.Handle);
// Check if we need to create a scaled ImageList for high DPI.
int currentDpi = DeviceDpi;
Size originalSize = _imageList.ImageSize;
Size scaledSize = ScaleHelper.ScaleToDpi(originalSize, currentDpi);

if (originalSize != scaledSize && _imageList.Images.Count > 0)
{
UpdateScaledImageList(currentDpi);
}
else
{
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, _imageList.Handle);
}
}

if (_stateImageList is not null)
Expand Down Expand Up @@ -2033,6 +2057,98 @@ protected override void OnHandleDestroyed(EventArgs e)
base.OnHandleDestroyed(e);
}

/// <summary>
/// Rescales constants when the DPI changes.
/// </summary>
protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew)
{
base.RescaleConstantsForDpi(deviceDpiOld, deviceDpiNew);

if (!IsHandleCreated)
{
return;
}

UpdateScaledImageList(deviceDpiNew);
}

/// <summary>
/// Creates or updates a scaled copy of the ImageList for the specified DPI.
/// </summary>
private void UpdateScaledImageList(int dpi)
{
if (_imageList is null || _imageList.Images.Count == 0)
{
ClearScaledImageList();
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, IntPtr.Zero);

return;
}

Size originalSize = _imageList.ImageSize;
Size scaledSize = ScaleHelper.ScaleToDpi(originalSize, dpi);

// If the sizes are the same, no scaling is needed - use the original list.
if (originalSize == scaledSize)
{
ClearScaledImageList();
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, _imageList.Handle);

return;
}

// We always recreate the scaled ImageList when rebuilding to avoid leaks
// and to keep it in sync with the source ImageList.
ClearScaledImageList();

_scaledImageList = new ImageList
{
ColorDepth = _imageList.ColorDepth,
ImageSize = scaledSize,
TransparentColor = _imageList.TransparentColor
};

// Copy and scale each image while preserving keys and indices.
for (int i = 0; i < _imageList.Images.Count; i++)
{
Image sourceImage = _imageList.Images[i];
string? key = _imageList.Images.Keys[i];

Image imageToAdd;

if (sourceImage is Bitmap bitmap)
{
imageToAdd = ScaleHelper.CopyAndScaleToSize(bitmap, scaledSize);
}
else
{
// Keep non-Bitmap images unscaled so indices stay aligned.
imageToAdd = (Image)sourceImage.Clone();
}

if (!string.IsNullOrEmpty(key))
{
_scaledImageList.Images.Add(key, imageToAdd);
}
else
{
_scaledImageList.Images.Add(imageToAdd);
}
}

_scaledImageListDpi = dpi;

// Send the scaled ImageList to the native control.
PInvokeCore.SendMessage(this, PInvoke.TVM_SETIMAGELIST, 0, _scaledImageList.Handle);
}

private void ClearScaledImageList()
{
_scaledImageList?.Dispose();
_scaledImageList = null;
_scaledImageListDpi = 0;
}

/// <summary>
/// We keep track of if we've hovered already so we don't fire multiple hover events
/// </summary>
Expand Down
Loading