Skip to content

DarkMode (c) fix TitleBar issues #13500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
86621ce
Introduce InitializeControl and refactor for DarkMode and InitialDPI …
KlausLoeffelmann Jun 3, 2025
cb94671
Introduce InitializeControl and refactor for DarkMode and InitialDPI …
KlausLoeffelmann Jun 3, 2025
ef26ca3
Fix DarkMode StatusStrip background renderer.
KlausLoeffelmann May 21, 2025
6a7e13f
Implement ToolStripSystemDarkModeRenderer.
KlausLoeffelmann Apr 24, 2025
7ee314d
Fix the StatusStripSizingGrip.
KlausLoeffelmann Apr 30, 2025
97db028
Change Grip renderer to use predefined shape.
KlausLoeffelmann May 8, 2025
870aea6
First batch of addressed review suggestions.
KlausLoeffelmann May 11, 2025
6df371c
Add GDI-Plus general optimization Co-Pilot instructions.
KlausLoeffelmann May 20, 2025
9d08d1e
Let Copilot address a series of review suggestions in GitHub.
Copilot May 20, 2025
566ccda
Refactor ToolStripRenderer classes and improve generic and GDIPlus Co…
KlausLoeffelmann May 22, 2025
256f9ea
Fix DarkMode StatusStrip background renderer.
KlausLoeffelmann Apr 9, 2025
f2b3dc9
Implement ToolStripSystemDarkModeRenderer.
KlausLoeffelmann Apr 24, 2025
c07c925
Fix bug where a dedicated Toolstrip Renderer would not be called for …
KlausLoeffelmann Apr 30, 2025
90e3413
Fix the StatusStripSizingGrip.
KlausLoeffelmann May 6, 2025
349a11f
Undo Experimental tagging of Form-APIs..
KlausLoeffelmann May 7, 2025
c5d37a8
Fix subtle bug in the SystemRenderer base class, and...
KlausLoeffelmann May 8, 2025
923b24c
Update ToolStrip renderers to address testing results and code review…
KlausLoeffelmann May 27, 2025
e0bda9b
Undo Experimental tagging of Form-APIs..
KlausLoeffelmann May 7, 2025
e78b4a7
Cleanup, refactor and optimize sharing renderer code.
KlausLoeffelmann May 7, 2025
1d9021b
First version of Prompt for DarkModeRenderer and class scaffolding.
KlausLoeffelmann May 1, 2025
f3289e6
Update ButtonDarkModeRenderer.
KlausLoeffelmann May 2, 2025
b2ce015
Prepare ButtonBase for DarkMode rendering.
KlausLoeffelmann May 2, 2025
f990271
Implement ButtonDarkModeAdapter.
KlausLoeffelmann May 2, 2025
e7c7cb5
Introduce dedicated handlers for the different FlatStyles.
KlausLoeffelmann May 13, 2025
4c82897
Refactor PopupButtonDarkModeRenderer
KlausLoeffelmann May 13, 2025
58d1326
Introduce AI-Prompt for refactoring Pens and Brushes to caching.
KlausLoeffelmann May 13, 2025
dd13611
Update rendering approaches.
KlausLoeffelmann May 13, 2025
05aec0f
Fix bug where other buttons would fail due to wrong adapter request.
KlausLoeffelmann May 16, 2025
f9f25df
Limit the DarkModeAdapter to Button-derived classes.
KlausLoeffelmann May 21, 2025
b8a0232
Adress generic Review feedback.
KlausLoeffelmann May 23, 2025
51c4c9a
Fix edge cases where dark mode did not get applied for ...
KlausLoeffelmann May 23, 2025
c9a122b
Fix DarkMode rendering in ComboBox/PropertyGrid. Also...
KlausLoeffelmann May 27, 2025
1e8fcd6
Do final code-style pass to meet all standards and improve copilot in…
KlausLoeffelmann May 27, 2025
bae464c
Address first batch of review suggestions.
KlausLoeffelmann May 28, 2025
1918a1e
Fix rebase/merge issue.
KlausLoeffelmann Jun 5, 2025
1eff594
Fix bug where a dedicated Toolstrip Renderer would not be called for …
KlausLoeffelmann Apr 30, 2025
9f8de68
Undo Experimental tagging of Form-APIs..
KlausLoeffelmann May 7, 2025
99a9cc6
Fix subtle bug in the SystemRenderer base class, and...
KlausLoeffelmann May 8, 2025
cadaf21
Update ToolStrip renderers to address testing results and code review…
KlausLoeffelmann May 27, 2025
addf0c3
Undo Experimental tagging of Form-APIs..
KlausLoeffelmann May 7, 2025
c10a0ee
Prepare ButtonBase for DarkMode rendering.
KlausLoeffelmann May 2, 2025
9839f1c
Fix edge cases where dark mode did not get applied for ...
KlausLoeffelmann May 23, 2025
306dada
Introduce AI-Prompt for refactoring Pens and Brushes to caching.
KlausLoeffelmann May 13, 2025
1213501
Apply title coloring attributes to TopLevelWindow after handle recrea…
KlausLoeffelmann May 5, 2025
2bd0340
Fix TitleBar edge case scenario when ShowInTaskbar recreates the whol…
KlausLoeffelmann May 26, 2025
665f147
Fix rebase merge issues.
KlausLoeffelmann Jun 5, 2025
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
318 changes: 318 additions & 0 deletions .github/Copilot/GDIPlus-copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
# WinForms GDI+ Best Practices: Performance, Quality, and Resource Management

This document provides comprehensive guidelines for optimizing GDI+ usage in the WinForms runtime. Following these practices ensures consistent performance, proper resource management, and high-quality rendering across the codebase.

## 1. Object Caching for Performance

### 1.1 Cached Pen and SolidBrush Objects

WinForms runtime provides a caching mechanism for `Pen` and `SolidBrush` objects to improve performance and reduce memory pressure. The underlying system uses:

```csharp
internal abstract partial class RefCountedCache<TObject, TCacheEntryData, TKey>
```

These cached objects have implicit conversions that make them behave like actual GDI+ types while significantly reducing allocation overhead.

### APIs, which already provide cached Pens or Brushes.

The APIs `SystemPens` and `SystemBrushes` already provide cached Pen/Brush objects.
- Make sure you prioritize their usage (except you need pen objects with a different width).
- If you use this API, these Pen or Brush object MUST NOT be disposed!

For example:
```CSharp
// Nothing to explicitly cache, since `SystemPens` already provides cached pen objects.
if (ToolStripManager.VisualStylesEnabled)
{
e.Graphics.DrawLine(SystemPens.ButtonHighlight, 0, bounds.Bottom - 1, bounds.Width, bounds.Bottom - 1);
e.Graphics.DrawLine(SystemPens.InactiveBorder, 0, bounds.Bottom - 2, bounds.Width, bounds.Bottom - 2);
}
```


### 1.2 Using Cached Objects

Always prefer cached objects over direct instantiation:

```csharp
// INCORRECT: Direct instantiation
using (var pen = new Pen(Color.Red))
{
g.DrawLine(pen, p1, p2);
}

// CORRECT: Using cached object
using var pen = Color.Red.GetCachedPenScope();
g.DrawLine(pen, p1, p2);
```

Available cached objects:

- **Pens:**
```csharp
// Default width (1)
using var pen = color.GetCachedPenScope();

// Custom width (integer only)
using var thickPen = color.GetCachedPenScope(2);
```

- **SolidBrushes:**
```csharp
using var brush = color.GetCachedSolidBrushScope();
```

Always use `var` for brevity and always apply `using` to ensure proper disposal.

IMPORTANT: You cannot use pen- or brush-scoped, when the pen needs additional features, for example, when during its lifetime, the pen needs to be modified (e.g., `Inset`, `DashStyle`, `CustomStartCap`, etc.). In these cases, you need to use the non-cached version of the pen or brush.

### 1.3 Transforming Helper Methods

When refactoring existing code:

- Identify methods that return `Pen` or `SolidBrush`
- For private helpers, change the return type to the corresponding cache-scope type
- For public/internal helpers, create new methods returning scope types while preserving the original methods

```csharp
// BEFORE
private Pen GetHighlightPen()
{
return new Pen(SystemColors.Highlight);
}

// AFTER
private PenCache.Scope GetHighlightPenScope()
=> SystemColors.Highlight.GetCachedPenScope();
```

## 2. GraphicsInternal Usage

### 2.1 When to Use GraphicsInternal

Always prefer `GraphicsInternal` over `Graphics` for performance improvements.

```csharp
// INCORRECT: Using Graphics directly
void Paint(PaintEventArgs e)
{
e.Graphics.DrawRectangle(pen, rect);
}

// CORRECT: Using GraphicsInternal
void Paint(PaintEventArgs e)
{
e.GraphicsInternal.DrawRectangle(pen, rect);
}
```

From the `PaintEventArgs` class:

```csharp
/// <summary>
/// For internal use to improve performance. DO NOT use this method if you modify the Graphics Clip or Transform.
/// </summary>
internal Graphics GraphicsInternal => _event.GetOrCreateGraphicsInternal(SaveStateIfNeeded);
```

IMPORTANT: While using `GraphicsInternal` should be used directly for performance improvements, avoid passing it around, since he callee would not be able to recognize the `GraphicsInternal` type.

### 2.2 State Management

If you must modify the clip or transform with `GraphicsInternal`, always implement proper state management:

```csharp
GraphicsState? previousState = null;
try
{
// Save the current state before modifying
previousState = graphicsInternal.Save();

// Now safe to modify the clip or transform
graphicsInternal.TranslateTransform(x, y);
graphicsInternal.SetClip(rect);

// Perform drawing operations
// ...
}
finally
{
// CRITICAL: Always restore the previous state
if (previousState is not null)
{
graphicsInternal.Restore(previousState);
}
}
```

## 3. Graphics Quality Settings

When changing graphics quality settings, always restore the previous setting:

```csharp
// Store original smoothing mode
SmoothingMode originalMode = g.SmoothingMode;

try
{
// Set temporary mode for specific drawing
g.SmoothingMode = SmoothingMode.AntiAlias;

// Perform drawing operations
// ...
}
finally
{
// Restore original mode when done
g.SmoothingMode = originalMode;
}
```

Always preserve and restore these settings:
- TextRenderingHint
- InterpolationMode
- CompositingQuality
- PixelOffsetMode
- SmoothingMode

## 4. Resource Management

### 4.1 Disposable Objects

Always properly dispose GDI+ objects that cannot be cached:

```csharp
// Objects with custom settings cannot be cached
using (var customPen = new Pen(Color.Red) { DashStyle = DashStyle.Dash })
{
g.DrawLine(customPen, p1, p2);
}
```

### 4.2 GraphicsPath Objects

Never cache GraphicsPath objects - always create, use, and dispose them locally:

```csharp
// CORRECT: Local creation and disposal
using (var path = new GraphicsPath())
{
path.AddEllipse(rect);
g.FillPath(brush, path);
}

// INCORRECT: Never store as a field
// private GraphicsPath _cachedPath; // BAD PRACTICE!
```

### 4.3 Avoiding Premature Disposal

Ensure objects are valid throughout their entire usage lifecycle:

```csharp
// INCORRECT: Potentially disposing before use
using (var brush = color.GetCachedSolidBrushScope())
{
someObject.SomeFutureOperation(brush); // brush might be disposed when used!
}

// CORRECT: Immediate usage within scope
using (var brush = color.GetCachedSolidBrushScope())
{
g.FillRectangle(brush, rect);
}
```


## 5. Complete Examples

### Basic Rendering with Cached Resources

```csharp
private void PaintControl(PaintEventArgs e)
{
// Use GraphicsInternal for better performance
var g = e.GraphicsInternal;

// Use cached pen and brush
using var borderPen = SystemColors.ActiveBorder.GetCachedPenScope();
using var fillBrush = SystemColors.Control.GetCachedSolidBrushScope();

// Draw with cached resources
g.FillRectangle(fillBrush, ClientRectangle);
g.DrawRectangle(borderPen, 0, 0, Width - 1, Height - 1);
}
```

### Complex Rendering with State Management

```csharp
private void DrawComplexControl(PaintEventArgs e)
{
GraphicsState? previousState = null;
SmoothingMode originalMode = SmoothingMode.Default;

try
{
var g = e.GraphicsInternal;

// Save state before modifications
previousState = g.Save();
originalMode = g.SmoothingMode;

// Apply quality settings
g.SmoothingMode = SmoothingMode.AntiAlias;

// Apply transform
g.TranslateTransform(10, 10);

// Use cached resources
using var pen = Color.Blue.GetCachedPenScope(2);
using var brush = Color.LightBlue.GetCachedSolidBrushScope();

// Create a path (never cache paths)
using (var path = new GraphicsPath())
{
path.AddEllipse(0, 0, 100, 50);
g.FillPath(brush, path);
g.DrawPath(pen, path);
}
}
finally
{
// Restore state
if (previousState is not null)
{
e.GraphicsInternal.Restore(previousState);
}

// Restore quality settings
e.GraphicsInternal.SmoothingMode = originalMode;
}
}
```

### Helper Method Transformation

```csharp
// BEFORE
private Pen CreateBorderPen()
{
return new Pen(SystemColors.ActiveBorder);
}

// AFTER
private PenCache.Scope GetBorderPenScope()
=> SystemColors.ActiveBorder.GetCachedPenScope();

// For public/internal APIs, create a new method and keep the original
internal Pen CreateHighlightPen()
{
return new Pen(SystemColors.Highlight);
}

// Add a new method returning the cached version
internal PenCache.Scope GetHighlightPenScope()
=> SystemColors.Highlight.GetCachedPenScope();
```
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.