In alternativa al file del gestionale si possono digitare i codici uno alla volta (Invio/Aggiungi, Canc per togliere). Tutto a valle legge solo _righeCorrenti, quindi basta una nuova sorgente; la selezione della griglia mantiene il colore verde/rosso della verifica. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
353 lines
19 KiB
C#
353 lines
19 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace PrepStep;
|
|
|
|
/// <summary>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.</summary>
|
|
public class GuidaForm : Form
|
|
{
|
|
private sealed record Sezione(
|
|
string Titolo,
|
|
string Intro,
|
|
string[]? Passi = null,
|
|
string? NotaTesto = null,
|
|
bool NotaAttenzione = false);
|
|
|
|
// 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",
|
|
"PrepStep - Cevolani 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 lo stato di distinta/raw/verifica per ripartire " +
|
|
"con una nuova distinta base, senza dover ricaricare il database."),
|
|
|
|
new("Dove finiscono i file",
|
|
"Tutto sta nella cartella di lavoro, che di default è la cartella di " +
|
|
"installazione (%LOCALAPPDATA%\\Programs\\PrepStep):",
|
|
Passi:
|
|
[
|
|
"Il database cad_index.db sta sempre nella cartella di installazione.",
|
|
"Il CSV di ogni distinta importata, \"raw\" (file CAD delle fasi 3-4) e " +
|
|
"\"step\" (STEP esportati da Creo, cartella che indichi tu a Creo) stanno " +
|
|
"nella cartella di lavoro. Il file .xls del gestionale resta dove l'hai " +
|
|
"preso.",
|
|
"Per cambiare la cartella di lavoro: \"File ▸ Scegli cartella di lavoro...\" " +
|
|
"(viene ricordata agli avvii successivi).",
|
|
"Per fare pulizia: \"File ▸ Pulisci raw e step...\" svuota \"raw\" e " +
|
|
"\"step\"; \"File ▸ Elimina CSV delle distinte...\" elimina i CSV, che " +
|
|
"altrimenti si accumulano uno per distinta. Nessuno dei due tocca il " +
|
|
"database.",
|
|
],
|
|
NotaTesto: "AppData è una cartella nascosta in Esplora file: per aprirla scrivi " +
|
|
"%LOCALAPPDATA%\\Programs\\PrepStep nella barra degli indirizzi (vale anche " +
|
|
"per scegliere \"step\" in Creo). Disinstallando il programma questi file " +
|
|
"restano: cancellali a mano se non servono più."),
|
|
|
|
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_<codice>_<timestamp>, l'app " +
|
|
"aggiunge da sola l'assieme generale come componente in più: non serve " +
|
|
"fare nulla di diverso.",
|
|
"Se non vuoi usare la cartella di installazione, scegli la cartella di " +
|
|
"lavoro (\"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",
|
|
"All'avvio l'app carica da sola \"cad_index.db\" dalla cartella di installazione. " +
|
|
"Un pallino verde/rosso indica se un database è caricato.",
|
|
Passi:
|
|
[
|
|
"Pallino rosso (nessun database): premi \"Aggiorna indice\" per scansionare " +
|
|
"le cartelle di primo livello della share (X:\\) e crearlo. La prima volta " +
|
|
"richiede qualche minuto, i giri successivi aggiornano solo quello che è " +
|
|
"cambiato.",
|
|
"Database da un'altra postazione: \"Apri DB...\" lo carica solo finché non " +
|
|
"chiudi il programma. Per usarlo sempre, copialo nella cartella di " +
|
|
"installazione come cad_index.db.",
|
|
]),
|
|
|
|
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. " +
|
|
"In alternativa scegli \"Lista personalizzata\" e digita i codici uno alla " +
|
|
"volta (Invio per aggiungere, Canc per togliere): nessun CSV, l'assieme " +
|
|
"generale va inserito a mano.",
|
|
"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): svuota " +
|
|
"le griglie e la verifica delle fasi 2-5 e riporta al passo 2, senza dover " +
|
|
"ricaricare il database né cambiare la cartella di lavoro. Non cancella nessun " +
|
|
"file su disco: CSV, \"raw\" e \"step\" restano.",
|
|
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...\"; per eliminare i CSV che ogni distinta importata lascia nella " +
|
|
"cartella di lavoro, usa \"File ▸ Elimina CSV delle distinte...\"."),
|
|
};
|
|
|
|
public GuidaForm()
|
|
{
|
|
Text = "Guida";
|
|
ClientSize = new Size(1000, 680);
|
|
MinimumSize = new Size(640, 440);
|
|
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 = 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);
|
|
};
|
|
|
|
var testo = new TestoSolaLettura
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
ReadOnly = true,
|
|
TabStop = false,
|
|
BorderStyle = BorderStyle.None,
|
|
BackColor = Color.White,
|
|
Font = new Font(Font.FontFamily, 10f),
|
|
};
|
|
|
|
lista.SelectedIndexChanged += (_, _) =>
|
|
{
|
|
if (lista.SelectedIndex >= 0)
|
|
ScriviSezione(testo, Sezioni[lista.SelectedIndex]);
|
|
};
|
|
|
|
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(24, 20, 24, 20), BackColor = Color.White };
|
|
contenuto.Controls.Add(testo);
|
|
|
|
Controls.Add(contenuto);
|
|
Controls.Add(lista);
|
|
Controls.Add(barraChiudi);
|
|
Controls.Add(headerPanel);
|
|
|
|
lista.SelectedIndex = 0;
|
|
}
|
|
|
|
/// <summary>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).</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>RichTextBox da sola lettura che non si comporta come un editor: niente cursore
|
|
/// lampeggiante, niente cursore a "I" e niente selezione col mouse. I clic nell'area di testo
|
|
/// vengono ignorati (così non prende il fuoco e non mostra il caret); la barra di scorrimento
|
|
/// sta nell'area non-client e resta utilizzabile, la rotella scorre al passaggio del mouse.</summary>
|
|
private sealed class TestoSolaLettura : RichTextBox
|
|
{
|
|
private const int WM_SETFOCUS = 0x0007;
|
|
private const int WM_SETCURSOR = 0x0020;
|
|
private const int WM_LBUTTONDOWN = 0x0201;
|
|
private const int WM_LBUTTONDBLCLK = 0x0203;
|
|
private const int WM_RBUTTONDOWN = 0x0204;
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool HideCaret(IntPtr hWnd);
|
|
|
|
protected override void WndProc(ref Message m)
|
|
{
|
|
switch (m.Msg)
|
|
{
|
|
case WM_LBUTTONDOWN or WM_LBUTTONDBLCLK or WM_RBUTTONDOWN:
|
|
return;
|
|
case WM_SETCURSOR:
|
|
Cursor.Current = Cursors.Arrow;
|
|
m.Result = 1;
|
|
return;
|
|
}
|
|
base.WndProc(ref m);
|
|
if (m.Msg == WM_SETFOCUS)
|
|
HideCaret(Handle);
|
|
}
|
|
}
|
|
}
|