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,89 @@
---
name: offscreen-winforms-render
description: À utiliser pour VÉRIFIER ou DÉBOGUER l'aspect visuel d'une IHM WinForms (.NET, y compris Syncfusion) quand il n'y a PAS de bureau interactif (agent, CI, headless) — où CopyFromScreen échoue. Rend la fenêtre HORS ÉCRAN dans un PNG (PrintWindow + thread STA) que l'on peut relire ET échantillonner au pixel (preuve empirique des couleurs/du layout). Inclut : forcer un état de survol, ouvrir une backstage, injecter une couleur de test.
---
# Rendu d'artefact offscreen (vérifier une IHM WinForms sans la lancer)
**But** : produire un **PNG** du rendu réel d'une fenêtre WinForms sans bureau interactif, pour le **relire**
et l'**échantillonner pixel par pixel** (preuve chiffrée). Portable : tout projet .NET WinForms (Syncfusion ou non).
## Pourquoi
- En agent/CI, pas de bureau interactif : `Graphics.CopyFromScreen` échoue (« Descripteur non valide »).
- Sans retour visuel, on tourne en rond sur les bugs d'IHM (couleur, layout).
- Parade : **dessiner la fenêtre hors écran dans un bitmap** via `PrintWindow`, sauver un PNG, puis (1) le relire,
(2) l'échantillonner (`bmp.GetPixel(x,y)`) pour comparer des valeurs RGB attendues.
## Prérequis
- Console jetable `net10.0-windows` (`OutputType=WinExe`, `UseWindowsForms=true`). Dossier préfixé `_` (gitignoré).
- Si l'IHM a une **licence** (Syncfusion…) : l'enregistrer, sinon un dialogue modal **gèle** le rendu.
- Si l'IHM a un **thème** chargé au démarrage : le charger pareil que l'app.
- Pour rendre la VRAIE fenêtre : référencer le projet applicatif (`ProjectReference`).
## Gabarit
```csharp
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
var th = new Thread(Run); // 1) thread STA obligatoire
th.SetApartmentState(ApartmentState.STA);
th.Start(); th.Join();
[DllImport("user32.dll")] static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags);
void Run()
{
// 2) Licence éventuelle (sinon dialogue d'essai modal => blocage)
// Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("...");
// 3) Thème éventuel, chargé comme dans l'app réelle
// SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly);
var f = new MyForm { StartPosition = FormStartPosition.Manual, Location = new Point(-3000, 0) }; // HORS écran
f.Show();
for (int i = 0; i < 10; i++) { Application.DoEvents(); Thread.Sleep(80); } // 4) laisser peindre (PAS Application.Run)
using var bmp = new Bitmap(f.Width, f.Height);
using (var g = Graphics.FromImage(bmp))
{
var hdc = g.GetHdc();
PrintWindow(f.Handle, hdc, 2); // 5) flag 2 = PW_RENDERFULLCONTENT (obligatoire)
g.ReleaseHdc(hdc);
}
bmp.Save(@"C:\chemin\rendu.png");
var c = bmp.GetPixel(450, 12); // 6) preuve empirique chiffrée
Console.WriteLine($"pixel = {c.R},{c.G},{c.B}");
f.Close();
}
```
## Recettes utiles
- **Plusieurs variantes en une exécution** : une fonction `Capture(name, build)` par variante, échantillonner le pixel décisif → comparer les RGB. Injecter une **couleur de test distinctive** (rouge/orange/violet), pas la couleur par défaut, pour distinguer « appliqué » de « inchangé ».
- **Rendre le VRAI form** (un mock minimal ne reproduit pas certains bugs de thème/skin) : référencer le projet, instancier la fenêtre. Accéder aux champs privés par réflexion si besoin :
```csharp
var fld = typeof(MyForm).GetField("backStageView", BindingFlags.NonPublic | BindingFlags.Instance);
```
- **Ouvrir une backstage Syncfusion** : `backStageView.ShowBackStage();`
- **Forcer un état de survol** (capturer un hover sans souris) : `button.State = ButtonAdvState.MouseOver;` puis `Invalidate()`. (⚠️ un `State` forcé n'est pas toujours fidèle à 100 % au survol réel selon le contrôle — recouper avec la valeur de propriété et/ou un test live.)
## Pièges (vérifiés)
- **Thread STA obligatoire** (`SetApartmentState(STA)`), sinon plantage à l'instanciation.
- **Licence avant tout** : sans enregistrement, le dialogue d'essai modal gèle le rendu.
- **Thème chargé** comme dans l'app, sinon rendu non représentatif.
- **`PrintWindow` flag = 2** (`PW_RENDERFULLCONTENT`) : `0`/`1` ratent les contrôles peints en différé (noir/partiel).
- **Fenêtre hors écran** (`Location` négatif + `StartPosition=Manual`).
- **Laisser peindre** : `Show()` + boucle `Application.DoEvents()`/`Sleep` (PAS `Application.Run`).
- **Coordonnées d'échantillonnage** : repérer les bons `(x,y)` en regardant d'abord le PNG (un contrôle peut être repositionné au runtime ≠ ses `Bounds` de design).
- **Pas de `CopyFromScreen`** en headless (échoue) — c'est ce que `PrintWindow` contourne.
## Workflow
1. Créer `_render/` (gitignoré).
2. Composer un contrôle minimal OU instancier la vraie fenêtre ; faire plusieurs variantes ; échantillonner.
3. **Relire** le PNG (juger l'aspect) **et** lire la sortie console (preuve chiffrée).
4. Itérer, puis **supprimer** le dossier jetable.