Initial : marketplace + 2 plugins (syncfusion-ribbon-accent, offscreen-winforms-render)

This commit is contained in:
Julien Cluzel
2026-06-08 19:30:32 +02:00
commit c3330204e3
8 changed files with 401 additions and 0 deletions
@@ -0,0 +1,11 @@
{
"$schema": "https://code.claude.com/docs/plugins/plugin-schema.json",
"name": "syncfusion-ribbon-accent",
"description": "Couleur d'accent personnalisée (live) sur un ruban Syncfusion WinForms Office2016 — ruban, backstage (dont le survol des items), onglets de contenu (TabControlAdv), grilles SfDataGrid. Donne le bon levier par élément et les pièges (ThemeStyle ignoré, ThemeName à conserver, BackStageItemHoverColor, FlattenHierarchy…). Réf. testée : Syncfusion WinForms 33.2.x.",
"version": "1.0.0",
"author": {
"name": "Julien Cluzel"
},
"license": "MIT",
"repository": "https://repos.ara-it.info/ARA-IT/ara-claude-plugins"
}
@@ -0,0 +1,195 @@
---
name: syncfusion-ribbon-accent
description: À utiliser pour IMPLÉMENTER ou DÉBOGUER une couleur d'accent personnalisée (live, choisie par l'utilisateur) sur une IHM Syncfusion WinForms Office2016 — ruban (RibbonControlAdv), backstage (BackStage/menu Fichier, y compris le SURVOL des items), onglets de contenu (TabControlAdv) et grilles (SfDataGrid). Donne le bon levier par élément et les pièges (ThemeStyle ignoré, ThemeName à ne pas vider, BackStageItemHoverColor, FlattenHierarchy…). Réf. testée : Syncfusion WinForms 33.2.x.
---
# Couleur d'accent sur un ruban Syncfusion WinForms (Office2016)
**But** : faire suivre une **couleur d'accent libre** à TOUTE l'IHM (ruban, backstage, onglets, grilles), en live.
**Testé** : Syncfusion Essential Studio **WinForms 33.2.10**, .NET 10, C#.
**Prérequis** : `SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly);` au démarrage.
## Règle d'or — chaque zone a SON mécanisme (ne pas les confondre)
| Élément | Bon levier (33.2.x) | À NE PAS faire |
|---|---|---|
| **Ruban** (bandeau, onglets, Fichier, boutons système) | `ribbon.Office2016ColorTable` (**Clear + Add d'UNE table**), en **gardant** `ThemeName="Office2016"` | `ribbon.ThemeStyle` (ignoré en live) ; vider `ThemeName` (ruban plat « touch ») |
| **Backstage — titre + bouton retour ←** | `ribbon.ThemeStyle.BackStageStyle` (`TitleBarBackColor`, `ControlBoxBackColor`…), **ré-assigner** `ribbon.ThemeStyle` | — |
| **Backstage — items (onglets+boutons) repos/SURVOL/sélection** | `ribbon.Office2016ColorTable.BackStageItem*` (le renderer `BackStage2016` lit cette table) | `BackStageButton.BackColor`/`MetroColor`/`OverrideFormManagedColor`/`ThemeStyle` (écrasés par le renderer) |
| **Backstage — onglet actif** | `backStage.ActiveTabColor` / `ActiveTabForeColor` | — |
| **Onglets de contenu** (`TabControlAdv`) | `content.ActiveTabColor` / `ActiveTabForeColor` | — |
| **Grilles** (`SfDataGrid`) | surcharge directe de `grid.Style` | chercher un `ThemeSettings` (inexistant en 33.2.10) |
| **QuickAccess Toolbar** (retrait) | `ribbon.QuickPanelVisible=false` + `ribbon.ShowQuickItemsDropDownButton=false` | — |
**3 principes** : (1) **garder** `ribbon.ThemeName="Office2016"` (le vider casse le rendu) ; (2) `Office2016ColorTable` est une `BindingList` **sans clé****une seule table** (`Clear()`+`Add()`) ; (3) le renderer Office2016 lit cette table pour le ruban **ET** la backstage.
## Pièges (à ne pas redécouvrir)
1. `ribbon.ThemeStyle` n'a **aucun effet live** sur le bandeau du ruban (le skin repeint par-dessus) → `Office2016ColorTable`.
2. Vider `ribbon.ThemeName` colore le bandeau MAIS retire le skin → rendu plat « touch », laid. **Garder `"Office2016"`.**
3. `Office2016ColorTable` = `BindingList` sans clé/scheme → **une seule entrée** suffit.
4. **Survol des items backstage** : ni `BackStageButton.BackColor`/`MetroColor`/`OverrideFormManagedColor`+`CustomManagedColor`/`Appearance=Metro`/`ButtonAdvVisualStyle.HoverBackColor` — le renderer peint et lit la **color table****`BackStageItemHoverColor`**.
5. `backStage.ThemeName` se fait **réécraser** par SfSkinManager (`BackStage2016Renderer`) ; le vider est inopérant.
6. **Noms `[Obsolete]`** : `BackStage*Tab*Color`/`BackStage*Button*Color` → unifiés en **`BackStageItem*`**.
7. `SfSkinManager` 33.2.10 = `LoadAssembly` + `SetTheme` seulement (pas de `ThemeSettings`/`RegisterThemeSettings`).
8. `Office2016ColorTable` ne s'applique que sous `Office2016ColorScheme.Colorful`.
9. **Réflexion incomplète = fausse conclusion** : lister les propriétés d'un type Syncfusion SANS `BindingFlags.FlattenHierarchy` rate les **membres hérités** (piège qui avait fait conclure à tort que `BackStageItemHoverColor` n'existait pas). **Toujours `Public|Instance|FlattenHierarchy`** + signaler les `[Obsolete]`.
## Vérification (environnement sans bureau / agent)
- **Rendre le VRAI form** offscreen (un mock minimal ne reproduit PAS le bleu backstage). `PrintWindow` (flag 2) + thread STA → PNG relisible + `bmp.GetPixel`. Cf. méthode de rendu offscreen.
- Ouvrir la backstage par réflexion : `backStageView.ShowBackStage()` (champ privé).
- **Forcer un survol** pour le capturer sans souris : `btnX.State = ButtonAdvState.MouseOver;` puis `Invalidate()`.
- Injecter une **couleur de test distinctive** (orange/rouge/violet), pas l'accent par défaut, pour distinguer « appliqué » de « inchangé ».
- **Vérifier l'API par réflexion AVANT de coder** (`MetadataLoadContext`, `FlattenHierarchy`).
## Checklist d'intégration
1. `SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly)`.
2. Ruban : `RibbonStyle.Office2016` + `Office2016ColorScheme.Colorful` + `ThemeName="Office2016"` + `CanOverrideStyle=true` + `MenuColor=accent`.
3. **Une** `Office2016ColorTable` (chrome=accent, corps clair, `BackStageItem*`=accent) → `Clear()`+`Add()`.
4. `ThemeStyle.BackStageStyle` : `TitleBarBackColor`/`ControlBoxBackColor` (+hover/pressed) → ré-assigner `ThemeStyle`.
5. `backStage.ActiveTabColor` + `content.ActiveTabColor`.
6. Grilles : `grid.Style.*` (sélection/en-tête/bordure/ascenseurs) + ré-application live.
7. QAT retirée si inutile.
8. Helpers `ForeColorFor` / `Shade` / `Tint`.
9. **Vérifier au rendu offscreen** (vrai form + survol forcé) avant de conclure.
## Implémentation de référence (copiable)
```csharp
using Syncfusion.Windows.Forms; // BackStage, BackStageButton
using Syncfusion.Windows.Forms.Tools; // RibbonForm, RibbonControlAdv, Office2016ColorTable, TabControlAdv
using Syncfusion.WinForms.Controls; // SfSkinManager
using Syncfusion.WinForms.DataGrid; // SfDataGrid
internal static class AppTheme
{
public static Color AccentColor { get; set; } = ColorTranslator.FromHtml("#0099CC");
public const string SfThemeName = "Office2016Colorful";
// === RUBAN + BACKSTAGE (titre/bouton retour) ===
public static void ApplyOffice2016(RibbonForm form, RibbonControlAdv ribbon, Color accent)
{
ribbon.RibbonStyle = RibbonStyle.Office2016;
ribbon.Office2016ColorScheme = Office2016ColorScheme.Colorful;
ribbon.ThemeName = "Office2016";
ribbon.CanOverrideStyle = true;
ribbon.MenuColor = accent;
ribbon.Office2016ColorTable.Clear();
ribbon.Office2016ColorTable.Add(BuildOffice2016ColorTable(accent));
Color fore = ForeColorFor(accent);
var themeStyle = ribbon.ThemeStyle;
var bs = themeStyle.BackStageStyle;
bs.TitleBarBackColor = accent; bs.TitleBarForeColor = fore;
bs.ControlBoxBackColor = accent; // bouton retour ←
bs.HoverControlBoxBackColor = Shade(accent, 0.12);
bs.PressedControlBoxBackColor = Shade(accent, 0.20);
bs.ItemBackColor = accent; bs.ItemForeColor = fore;
bs.SelectedItemBackColor = Shade(accent, 0.20); bs.SelectedItemForeColor = fore;
bs.HoverItemBackColor = Shade(accent, 0.12); bs.HoverItemForeColor = fore;
ribbon.ThemeStyle = themeStyle;
ribbon.Refresh();
ribbon.Invalidate(true);
}
private static Office2016ColorTable BuildOffice2016ColorTable(Color accent)
{
Color fore = ForeColorFor(accent);
Color body = ColorTranslator.FromHtml("#F1F1F1");
Color bodyText = ColorTranslator.FromHtml("#575757");
Color border = ColorTranslator.FromHtml("#D2D2D2");
Color hover = ColorTranslator.FromHtml("#C5C5C5");
return new Office2016ColorTable
{
// Chrome à l'accent
HeaderColor = accent, TabBackColor = accent, TitleColor = fore,
TabForeColor = fore, HoverTabForeColor = fore, SelectedTabColor = accent,
CheckedTabColor = body, CheckedTabForeColor = accent,
FileMenuForeColor = fore, FileMenuHoverForeColor = fore,
SystemButtonBackground = accent, ControlBoxForeColor = fore,
CloseButtonBackground = ColorTranslator.FromHtml("#E81123"),
// Items backstage (le renderer lit cette table) — SURVOL = BackStageItemHoverColor
BackStageItemForeColor = fore,
BackStageItemHoverColor = Shade(accent, 0.16),
BackStageItemHoverForeColor = fore,
BackStageItemSelectionColor = Shade(accent, 0.26),
BackStageItemSelectionForeColor = fore,
BackStageTitleColor = ColorTranslator.FromHtml("#3A3A3A"),
// Corps clair, lisible
CaptionBackColor = body, CaptionForeColor = bodyText,
ToolStripBackColor = body, PanelBackColor = body, RibbonPanelBorderColor = border,
ToolStripItemForeColor = bodyText, ToolStripSpliterColor = border,
ButtonBackgroundHovered = hover, ButtonBackgroundSelected = hover,
ButtonBackgroundPressed = ColorTranslator.FromHtml("#969696"),
};
}
// === ONGLETS DE CONTENU ===
public static void ApplyContentTabs(TabControlAdv content, Color accent)
{
content.ActiveTabColor = accent;
content.ActiveTabForeColor = ForeColorFor(accent);
content.Refresh(); content.Invalidate(true);
}
// === BACKSTAGE : onglet actif (le reste via la color table) ===
public static void ApplyBackStageItems(BackStage backStage, Color accent)
{
backStage.ActiveTabColor = accent;
backStage.ActiveTabForeColor = ForeColorFor(accent);
backStage.Invalidate(true);
}
// === GRILLES ===
public static void ApplySfControlTheme(Control control) => SfSkinManager.SetTheme(control, SfThemeName);
public static void ApplyGridAccent(SfDataGrid grid, Color accent)
{
grid.Style.SelectionStyle.BackColor = Tint(accent, 0.55);
grid.Style.SelectionStyle.TextColor = Color.Black;
grid.Style.HeaderStyle.SortIconColor = accent;
grid.Style.HeaderStyle.FilterIconColor = accent;
grid.Style.BorderColor = accent;
grid.Style.VerticalScrollBar.ThumbColor = Tint(accent, 0.45);
grid.Style.VerticalScrollBar.ThumbHoverColor = accent;
grid.Style.HorizontalScrollBar.ThumbColor = Tint(accent, 0.45);
grid.Style.HorizontalScrollBar.ThumbHoverColor = accent;
grid.Invalidate();
}
public static void RefreshGridAccents(Control root, Color accent)
{
foreach (Control c in root.Controls)
{
if (c is SfDataGrid g) ApplyGridAccent(g, accent);
if (c.HasChildren) RefreshGridAccents(c, accent);
}
}
// === HELPERS ===
private static Color ForeColorFor(Color c)
=> (0.299 * c.R + 0.587 * c.G + 0.114 * c.B) / 255.0 > 0.55 ? Color.Black : Color.White;
public static Color Tint(Color c, double t) => Color.FromArgb(c.A,
(int)(c.R + (255 - c.R) * t), (int)(c.G + (255 - c.G) * t), (int)(c.B + (255 - c.B) * t));
private static Color Shade(Color c, double f) => Color.FromArgb(c.A,
(int)(c.R * (1 - f)), (int)(c.G * (1 - f)), (int)(c.B * (1 - f)));
}
```
### Câblage (au démarrage ET dans le picker de couleur)
```csharp
AppTheme.ApplyOffice2016(this, ribbon, accent);
AppTheme.ApplyContentTabs(content, accent);
AppTheme.ApplyBackStageItems(backStage, accent);
AppTheme.RefreshGridAccents(content, accent); // grilles déjà ouvertes
// QAT : ribbon.QuickPanelVisible = false; ribbon.ShowQuickItemsDropDownButton = false;
// + persister la couleur et la relire au démarrage
```
> Guide long + récit complet : `docs/syncfusion-ribbon-couleur-accent.md` (dans le repo d'origine CapToilettage).
> Technique de vérification : `docs/methode-rendu-offscreen.md`.