Initial : marketplace + 2 plugins (syncfusion-ribbon-accent, offscreen-winforms-render)
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user