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
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public partial class ListView : Control
private ImageList? _imageListSmall;
private ImageList? _imageListState;
private ImageList? _imageListGroup;
private ImageList? _darkModeStateImageList;

private MouseButtons _downButton;
private int _itemCount;
Expand Down Expand Up @@ -3066,6 +3067,9 @@ protected override void Dispose(bool disposing)
DetachGroupImageListHandlers();
_imageListGroup = null;

_darkModeStateImageList?.Dispose();
_darkModeStateImageList = null;

// Remove any ColumnHeaders contained in this control
if (_columnHeaders is not null)
{
Expand Down Expand Up @@ -4703,9 +4707,84 @@ private void ApplyDarkModeOnDemand()
columnHeaderHandle,
$"{DarkModeIdentifier}_{ItemsViewThemeIdentifier}",
null);

if (CheckBoxes)
{
UpdateDarkModeCheckBoxImages();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be needed in dark mode.

}
}
}

private void UpdateDarkModeCheckBoxImages()
{
if (_imageListState is not null || !CheckBoxes || !IsHandleCreated)
{
return;
}

_darkModeStateImageList?.Dispose();

int size = ScaleHelper.ScaleToInitialSystemDpi(16);

ImageList imageList = new()
{
ImageSize = new Size(size, size),
ColorDepth = ColorDepth.Depth32Bit,
TransparentColor = Color.Transparent
};

imageList.Images.Add(CreateDarkModeCheckBoxBitmap(isChecked: false, size));
imageList.Images.Add(CreateDarkModeCheckBoxBitmap(isChecked: true, size));

// Ensure the checkbox extended style is active before assigning our custom state imagelist,
// otherwise the style refresh may overwrite the list with the default light-themed images.
ForceCheckBoxUpdate();

PInvokeCore.SendMessage(
this,
PInvoke.LVM_SETIMAGELIST,
(WPARAM)PInvoke.LVSIL_STATE,
(LPARAM)imageList.Handle);

_darkModeStateImageList = imageList;

if (VirtualMode)
{
return;
}

if (Items.Count > 0)
{
const LIST_VIEW_ITEM_STATE_FLAGS stateMask = LIST_VIEW_ITEM_STATE_FLAGS.LVIS_STATEIMAGEMASK;

for (int i = 0; i < Items.Count; i++)
{
bool isChecked = Items[i].Checked;
int stateIndex = isChecked ? 2 : 1;

SetItemState(i, (LIST_VIEW_ITEM_STATE_FLAGS)(stateIndex << 12), stateMask);
}

PInvokeCore.SendMessage(
this,
PInvoke.LVM_REDRAWITEMS,
(WPARAM)0,
(LPARAM)(Items.Count - 1));
}
}

private Bitmap CreateDarkModeCheckBoxBitmap(bool isChecked, int size)
{
Bitmap bitmap = new(size, size);

using Graphics graphics = Graphics.FromImage(bitmap);
graphics.Clear(Color.Transparent);
CheckBoxState state = isChecked ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;
CheckBoxRenderer.DrawCheckBoxWithVisualStyles(graphics, new Point(0, 0), state, HWNDInternal);

return bitmap;
}

protected override void OnHandleDestroyed(EventArgs e)
{
// don't save the list view items state when in virtual mode : it is the responsibility of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ public bool CheckBoxes
if (CheckBoxes)
{
UpdateStyles();
UpdateCheckBoxImages();
}
else
{
Expand Down Expand Up @@ -1869,6 +1870,7 @@ protected override void OnHandleCreated(EventArgs e)
int style = (int)PInvokeCore.GetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
style |= (int)PInvoke.TVS_CHECKBOXES;
PInvokeCore.SetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style);
UpdateCheckBoxImages();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only be needed in dark mode?

}

if (ShowNodeToolTips && !DesignMode)
Expand Down Expand Up @@ -1991,6 +1993,43 @@ private void UpdateNativeStateImageList()
_internalStateImageList = newImageList;
}

private void UpdateCheckBoxImages()
{
if (DesignMode || !IsHandleCreated || !CheckBoxes || _stateImageList is not null)
{
return;
}

int size = StateImageSize?.Width ?? ScaleHelper.ScaleToInitialSystemDpi(16);
ImageList imageList = new()
{
ImageSize = new Size(size, size),
ColorDepth = ColorDepth.Depth32Bit
};

using Bitmap uncheckedBitmap = new(size, size);
using (Graphics g = Graphics.FromImage(uncheckedBitmap))
{
CheckBoxRenderer.DrawCheckBoxWithVisualStyles(g, new Point(0, 0), CheckBoxState.UncheckedNormal, HWNDInternal);
}

using Bitmap checkedBitmap = new(size, size);
using (Graphics g = Graphics.FromImage(checkedBitmap))
{
CheckBoxRenderer.DrawCheckBoxWithVisualStyles(g, new Point(0, 0), CheckBoxState.CheckedNormal, HWNDInternal);
}

// TreeView uses 1-based indexing: index 0=placeholder, 1=unchecked, 2=checked
imageList.Images.Add(new Bitmap(size, size)); // Placeholder
imageList.Images.Add(uncheckedBitmap);
imageList.Images.Add(checkedBitmap);

SetStateImageList(imageList.Handle);

_internalStateImageList?.Dispose();
_internalStateImageList = imageList;
}

private void SetStateImageList(IntPtr handle)
{
// In certain cases (TREEVIEWSTATE_checkBoxes) e.g., the Native TreeView leaks the imageList
Expand Down Expand Up @@ -3385,6 +3424,7 @@ protected override unsafe void WndProc(ref Message m)
case PInvokeCore.WM_SYSCOLORCHANGE:
PInvokeCore.SendMessage(this, PInvoke.TVM_SETINDENT, (WPARAM)Indent);
base.WndProc(ref m);
UpdateCheckBoxImages();
break;
case PInvokeCore.WM_SETFOCUS:
// If we get focus through the LButtonDown .. we might have done the validation...
Expand Down