From d81fede4cc393fc6271975b52c22fec4ac1d103d Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 22 Sep 2026 16:29:42 +0200 Subject: [PATCH] Ridisegna la finestra Guida con gerarchia visiva e callout colorati MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La guida in-app era testo semplice senza gerarchia (stesso peso per titoli, corpo, passi, avvisi) e una sidebar con la selezione blu di sistema, fuori stile rispetto al resto dell'app. Sezioni ristrutturate come dati (Titolo/Intro/Passi/Nota) invece di un unico blob di testo libero, e renderizzate con gerarchia esplicita nel RichTextBox: titolo blu primario grassetto, passi numerati con il numero in blu, callout "Nota"/"Attenzione" con sfondo a tinta piatta (blu tenue per le info, ambra tenue per gli avvisi che fanno perdere tempo se ignorati, come l'errore Creo "[-4]"). Sidebar con selezione disegnata a mano nel blu dell'app invece del blu di sistema. Bottone Chiudi spostato sulla stessa floatbar già usata nelle altre finestre. Co-Authored-By: Claude Sonnet 5 --- PrepStep/GuidaForm.cs | 292 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 238 insertions(+), 54 deletions(-) diff --git a/PrepStep/GuidaForm.cs b/PrepStep/GuidaForm.cs index d07fa62..429a912 100644 --- a/PrepStep/GuidaForm.cs +++ b/PrepStep/GuidaForm.cs @@ -1,71 +1,195 @@ namespace PrepStep; -/// Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): sintesi delle 5 fasi del -/// wizard, per chi apre l'app senza aver letto i docs. Il dettaglio completo (formato .asm, -/// schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui. +/// Guida interattiva richiamabile da "Aiuto ▸ Guida" (F1): stessa guida completa di +/// docs/guida-utente.md (passo-passo fino a Creo Distributed Batch), pensata per chi apre +/// l'app senza aver letto i docs e senza esperienza pregressa. Il dettaglio tecnico (formato +/// .asm, schema DB, ecc.) resta solo in docs/architettura.md e affini, per non duplicarlo qui. +/// +/// Gerarchia tipografica nel pannello di lettura (vedi ScriviSezione): titolo sezione in blu +/// primario/grassetto/14pt, corpo in grigio quasi-nero/10pt, passi numerati con il numero in +/// blu grassetto per farli scorrere a colpo d'occhio, callout con sfondo tinta piatta (nessun +/// bordo/ombra: RichTextBox non li supporta) — blu tenue per le note informative, ambra tenue +/// per gli avvisi importanti, con etichetta ("Nota"/"Attenzione") in grassetto a inizio blocco +/// invece di un colore isolato, così restano leggibili anche senza vedere i colori. public class GuidaForm : Form { - private static readonly (string Titolo, string Testo)[] Sezioni = - { - ("Panoramica", """ - Cevolani StepPrep prepara i file CAD Pro-ENGINEER/Creo da passare a Creo - Distributed Batch per l'esportazione STEP: indicizza i file di una share di - rete e guida attraverso 5 fasi, una alla volta, con "Avanti"/"Indietro". + private sealed record Sezione( + string Titolo, + string Intro, + string[]? Passi = null, + string? NotaTesto = null, + bool NotaAttenzione = false); - "File ▸ Nuovo" (Ctrl+N) azzera distinta/raw/verifica per - ripartire con una nuova distinta base, senza dover ricaricare il database. - """), - ("1. Database", """ - Carica o aggiorna l'indice "cad_index.db" a partire dalle cartelle di primo - livello del disco/share (radice di default X:\). Un pallino verde/rosso indica - se un database è caricato. La cartella di lavoro, dove finiscono CSV, "raw" e - "step", si cambia da "File ▸ Scegli cartella di lavoro...". - """), - ("2. Importa Distinta Base", """ - Converte il file della distinta (trascinabile nella finestra, o scelto col - bottone "1. Carica la distinta") in un CSV componente;descrizione nella cartella - di lavoro. Se il file segue la convenzione del gestionale, aggiunge anche - l'assieme generale come riga. Poi "2. Verifica la presenza dei file sul server" - cerca i componenti nell'indice: è questo passo, non il solo import, a sbloccare - "Avanti". - """), - ("3. Crea cartella raw", """ - Cerca nell'indice i file CAD dei componenti del CSV e li copia nella - sottocartella "raw" della cartella di lavoro. - """), - ("4. Trova componenti referenziati", """ - Legge i file .asm già in "raw" per risolvere ricorsivamente sotto-assiemi e - parti citate, copiando anche quelli in "raw": componenti soppressi (non in - distinta ma necessari per aprire l'assieme) e riferimenti esterni (assembly - feature, datum copiati) inclusi. - """), - ("5. Verifica completezza", """ - Mostra i file .asm della distinta già in "raw", trascinabili su Creo - Distributed Batch per l'esportazione STEP (passo manuale, fuori da - quest'app): esporta in "step", dentro la cartella di lavoro. Se Distributed - Batch fallisce con "[-4] Dati di input non completi", quasi sempre manca un - file nell'albero dei componenti: torna alla fase 4 e controlla i "non - trovati". - """), + // Palette: blu primario dell'app per titoli/passi, ambra tenue solo per gli avvisi che + // possono far perdere tempo se ignorati (es. l'errore Creo [-4]) — il rosso resta riservato + // ai messaggi di errore veri e propri altrove nell'app. + private static readonly Color ColoreTesto = Color.FromArgb(40, 40, 40); + private static readonly Color ColoreNotaInfoSfondo = Color.FromArgb(232, 240, 250); + private static readonly Color ColoreNotaInfoTesto = Color.FromArgb(15, 65, 120); + private static readonly Color ColoreNotaAttenzioneSfondo = Color.FromArgb(253, 240, 219); + private static readonly Color ColoreNotaAttenzioneTesto = Color.FromArgb(140, 90, 10); + + private static readonly Sezione[] Sezioni = + { + new("Panoramica", + "Cevolani StepPrep prepara i file CAD Pro-ENGINEER/Creo da passare a Creo " + + "Distributed Batch per l'esportazione STEP: indicizza i file di una share di rete " + + "e guida attraverso 5 fasi, una alla volta, con \"Avanti\"/\"Indietro\".", + NotaTesto: "\"File ▸ Nuovo\" (Ctrl+N) azzera distinta/raw/verifica per ripartire " + + "con una nuova distinta base, senza dover ricaricare il database."), + + new("Prima di iniziare", + "Prima di importare la prima distinta:", + Passi: + [ + "Serve accesso alla share di rete con i file CAD (di default X:\\).", + "Serve l'estrazione \"Distinta Base\" da Fata (il gestionale): il file .xls " + + "così come Fata lo esporta, senza risalvarlo o convertirlo in altri " + + "formati — l'app riconosce da sola se sotto l'estensione .xls c'è XML o " + + "Excel binario vero.", + "Se il file segue la convenzione di nome DBP__, l'app " + + "aggiunge da sola l'assieme generale come componente in più: non serve " + + "fare nulla di diverso.", + "Conviene impostare la cartella di lavoro giusta (fase 1, o \"File ▸ Scegli " + + "cartella di lavoro...\") prima di importare la distinta: è lì che " + + "finiscono CSV, \"raw\" e, dopo l'esportazione con Creo, \"step\".", + "Se la cartella di lavoro è già stata usata per un'altra distinta, conviene " + + "pulire \"raw\" e \"step\" prima di ripartire (\"File ▸ Pulisci raw e " + + "step...\"): \"File ▸ Nuovo\" azzera solo lo stato dell'app, non i file " + + "già copiati su disco.", + ]), + + new("1. Database", + "Carica o aggiorna l'indice \"cad_index.db\" a partire dalle cartelle di primo " + + "livello del disco/share (radice di default X:\\). Un pallino verde/rosso indica " + + "se un database è caricato. La cartella di lavoro, dove finiscono CSV, \"raw\" e " + + "\"step\", si cambia da \"File ▸ Scegli cartella di lavoro...\"."), + + new("2. Importa Distinta Base", + "Due passi in sequenza:", + Passi: + [ + "Carica la distinta: trascina il file .xls nella finestra, oppure premi " + + "\"Scegli file distinta...\". Le righe importate compaiono nella griglia " + + "(Componente/Descrizione) e viene scritto un CSV nella cartella di lavoro.", + "Verifica la presenza dei file sul server: compare solo dopo un import " + + "riuscito. Cerca ogni componente nel database — le righe della griglia si " + + "colorano di verde (trovato) o rosso (non trovato nell'indice).", + ], + NotaTesto: "\"Avanti\" si sblocca solo dopo il passo 2, non basta il solo import: " + + "è il modo per accorgersi subito se il database è disallineato rispetto alla " + + "share prima di andare avanti.", + NotaAttenzione: true), + + new("3. Crea cartella raw", + "Premi \"Crea cartella raw\": per ogni componente della distinta trovato " + + "nell'indice, l'app ne copia il file CAD dalla share nella cartella \"raw\" " + + "(dentro la cartella di lavoro). La griglia mostra il percorso CAD copiato per " + + "ogni componente."), + + new("4. Trova componenti referenziati", + "Gli assiemi (.asm) referenziano altri file (sotto-assiemi, parti, componenti " + + "soppressi, riferimenti esterni) che Creo deve poter caricare insieme " + + "all'assieme principale, anche se non compaiono nella distinta del gestionale. " + + "Premi \"Trova componenti referenziati\": l'app legge i file .asm già in \"raw\", " + + "risolve ricorsivamente tutto l'albero e copia anche quei file in \"raw\".\n\n" + + "Si apre una finestra con l'elenco di quanto trovato e copiato, con in fondo " + + "eventuali codici non corrispondenti a nessun file indicizzato (possono essere " + + "disegni, componenti davvero mancanti, o falsi positivi — vale la pena " + + "controllarli). Da lì si può anche aprire direttamente la cartella \"raw\".", + NotaTesto: "Passo opzionale se la distinta contiene solo parti singole senza " + + "sotto-assiemi: in quel caso si può passare direttamente alla fase successiva."), + + new("5. Verifica completezza", + "Mostra i file .asm della distinta già in \"raw\", trascinabili. Da qui il " + + "passaggio a Creo è manuale, fuori da quest'app:", + Passi: + [ + "Apri Creo Distributed Batch (dal menu Start di Windows, o dal file " + + "ptcdbatch.bat nella cartella bin dell'installazione Creo).", + "Crea (o riusa, se già presente in azienda) un gruppo di attività per " + + "l'esportazione STEP: menu Actions ▸ New Task Group.", + "Aggiungi i file: trascina i .asm dalla finestra dell'app (o dalla cartella " + + "\"raw\" in Esplora risorse) sul gruppo di attività, oppure sfoglia e " + + "selezionali a mano.", + "Verifica che il gruppo di attività punti al config.pro/profilo di export " + + "STEP giusto (sezione Configuration Files) — di norma è già impostato " + + "una volta sola per tutti.", + "Avvia l'elaborazione (Start/Run): Distributed Batch legge ogni .asm, con " + + "tutto l'albero di sotto-componenti già raccolto nelle fasi 3-4, ed " + + "esporta i file STEP nella cartella \"step\" (dentro la cartella di " + + "lavoro, accanto a \"raw\").", + ], + NotaTesto: "Se Distributed Batch fallisce con \"[-4] Dati di input non " + + "completi\", quasi sempre manca un file nell'albero dei componenti — Creo " + + "non dice quale. Torna alla fase 4 e controlla l'elenco dei \"non trovati\": " + + "è il punto di partenza per capire cosa recuperare prima di riprovare.", + NotaAttenzione: true), + + new("Ripartire da capo", + "Quando devi processare un'altra distinta, usa \"File ▸ Nuovo\" (Ctrl+N): azzera " + + "CSV, raw e verifica delle fasi 2-5 e riporta al passo 2, senza dover ricaricare " + + "il database o la cartella di lavoro (restano quelli già impostati in fase 1).", + NotaTesto: "Per ripulire le cartelle \"raw\" e \"step\" della cartella di lavoro " + + "prima di un nuovo giro (senza toccare l'indice), usa \"File ▸ Pulisci raw e " + + "step...\"."), }; public GuidaForm() { Text = "Guida"; - ClientSize = new Size(640, 420); - MinimumSize = new Size(480, 320); + ClientSize = new Size(780, 520); + MinimumSize = new Size(580, 400); StartPosition = FormStartPosition.CenterParent; Font = new Font("Segoe UI", 9.5f); + try + { + Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); + } + catch (Exception ex) when (ex is IOException or ArgumentException) + { + // ponytail: nessuna icona nell'exe (es. lanciato da dotnet run) -> resta quella di default + } + var headerPanel = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = SystemColors.ControlLight }; + var lblHeader = new Label + { + Dock = DockStyle.Fill, + Text = "Guida", + TextAlign = ContentAlignment.MiddleLeft, + Padding = new Padding(16, 0, 0, 0), + Font = new Font(Font, FontStyle.Bold), + }; + headerPanel.Controls.Add(lblHeader); + + // Sidebar disegnata a mano (OwnerDrawFixed) invece della selezione blu di sistema: + // stessa identità visiva del resto dell'app (UiStile.ColorePrimario) invece del colore + // di selezione standard di Windows, che varia con il tema dell'utente. var lista = new ListBox { Dock = DockStyle.Left, - Width = 200, + Width = 230, IntegralHeight = false, BorderStyle = BorderStyle.None, + DrawMode = DrawMode.OwnerDrawFixed, + ItemHeight = 34, + BackColor = UiStile.ColoreSfondoCard, + }; + foreach (var sezione in Sezioni) + lista.Items.Add(sezione.Titolo); + lista.DrawItem += (_, e) => + { + if (e.Index < 0) + return; + bool selezionato = (e.State & DrawItemState.Selected) != 0; + using var sfondo = new SolidBrush(selezionato ? UiStile.ColorePrimario : UiStile.ColoreSfondoCard); + e.Graphics.FillRectangle(sfondo, e.Bounds); + var colore = selezionato ? Color.White : ColoreTesto; + using var testoFont = new Font(lista.Font, selezionato ? FontStyle.Bold : FontStyle.Regular); + var rettangoloTesto = new Rectangle(e.Bounds.X + 16, e.Bounds.Y, e.Bounds.Width - 20, e.Bounds.Height); + TextRenderer.DrawText(e.Graphics, lista.Items[e.Index].ToString(), testoFont, rettangoloTesto, + colore, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); }; - foreach (var (titolo, _) in Sezioni) - lista.Items.Add(titolo); var testo = new RichTextBox { @@ -79,19 +203,79 @@ public class GuidaForm : Form lista.SelectedIndexChanged += (_, _) => { if (lista.SelectedIndex >= 0) - testo.Text = Sezioni[lista.SelectedIndex].Testo.Trim(); + ScriviSezione(testo, Sezioni[lista.SelectedIndex]); }; - var btnChiudi = new Button { Text = "Chiudi", Dock = DockStyle.Bottom, Height = 34 }; - UiStile.Secondario(btnChiudi); + var (barraChiudi, azioniChiudi) = UiStile.FloatBar(); + var btnChiudi = new Button { Text = "Chiudi" }; + UiStile.Primario(btnChiudi); + UiStile.Arrotonda(btnChiudi, 10); btnChiudi.Click += (_, _) => Close(); + azioniChiudi.Controls.Add(btnChiudi); + CancelButton = btnChiudi; - var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(16) }; + var contenuto = new Panel { Dock = DockStyle.Fill, Padding = new Padding(24, 20, 24, 20), BackColor = Color.White }; contenuto.Controls.Add(testo); + Controls.Add(contenuto); Controls.Add(lista); - Controls.Add(btnChiudi); + Controls.Add(barraChiudi); + Controls.Add(headerPanel); lista.SelectedIndex = 0; } + + /// Costruisce il pannello di lettura con gerarchia tipografica esplicita: titolo + /// (blu primario, grassetto, 14pt), corpo (grigio quasi-nero, 10pt), passi numerati (numero + /// in blu grassetto, indentati) e callout finale con sfondo a tinta piatta ed etichetta in + /// grassetto — informativo (blu tenue) o di attenzione (ambra tenue). Applica la + /// formattazione DOPO aver scritto il testo, selezionando il range appena aggiunto: è il + /// solo modo affidabile di formattare i paragrafi in un RichTextBox (impostare + /// SelectionFont/Color su una selezione vuota prima di AppendText non è garantito che + /// "si attacchi" al testo che segue). + private static void ScriviSezione(RichTextBox box, Sezione sezione) + { + box.Clear(); + var titoloFont = new Font(box.Font.FontFamily, 14f, FontStyle.Bold); + var corpoFont = new Font(box.Font.FontFamily, 10f, FontStyle.Regular); + var corpoGrassetto = new Font(box.Font.FontFamily, 10f, FontStyle.Bold); + + void Scrivi(string testo, Font font, Color colore, int indent = 0, Color? sfondo = null) + { + int inizio = box.TextLength; + box.AppendText(testo); + box.Select(inizio, box.TextLength - inizio); + box.SelectionFont = font; + box.SelectionColor = colore; + box.SelectionBackColor = sfondo ?? box.BackColor; + box.SelectionIndent = indent; + } + + Scrivi(sezione.Titolo + "\n\n", titoloFont, UiStile.ColorePrimario); + Scrivi(sezione.Intro + "\n", corpoFont, ColoreTesto); + + if (sezione.Passi is { Length: > 0 }) + { + Scrivi("\n", corpoFont, ColoreTesto); + for (int i = 0; i < sezione.Passi.Length; i++) + { + Scrivi($"{i + 1}. ", corpoGrassetto, UiStile.ColorePrimario, indent: 8); + Scrivi(sezione.Passi[i] + "\n\n", corpoFont, ColoreTesto, indent: 8); + } + } + + if (sezione.NotaTesto is not null) + { + var sfondo = sezione.NotaAttenzione ? ColoreNotaAttenzioneSfondo : ColoreNotaInfoSfondo; + var coloreEtichetta = sezione.NotaAttenzione ? ColoreNotaAttenzioneTesto : ColoreNotaInfoTesto; + string etichetta = sezione.NotaAttenzione ? "Attenzione" : "Nota"; + + Scrivi("\n", corpoFont, ColoreTesto); + Scrivi(" " + etichetta + "\n", corpoGrassetto, coloreEtichetta, indent: 12, sfondo: sfondo); + Scrivi(" " + sezione.NotaTesto + " \n", corpoFont, ColoreTesto, indent: 12, sfondo: sfondo); + } + + box.SelectionStart = 0; + box.SelectionLength = 0; + } }