🎯 Progetto Finale: Gestionale Biblioteca

Metti insieme TUTTE le competenze in un’app professionale completa!

🚀 Il Grande Progetto Finale

Hai imparato tutti i controlli di Windows Forms! Ora è il momento di creare
un’applicazione completa e professionale che integra tutto quello
che hai studiato. Questo progetto andrà nel tuo portfolio! 💼✨

📚 Il Progetto: Gestionale Biblioteca

Creerai un sistema completo per gestire una biblioteca: catalogo libri,
gestione prestiti, statistiche, ricerca avanzata, salvataggio dati.
Un’applicazione che potresti usare DAVVERO!

🎯 Requisiti del Progetto

✅ Funzionalità Base

  • Login con credenziali
  • CRUD libri (Create, Read, Update, Delete)
  • Gestione prestiti
  • Ricerca e filtri
  • Salvataggio su file CSV
✅ Controlli da Usare

  • TabControl (organizzazione)
  • DataGridView (visualizza dati)
  • MenuStrip (menu professionale)
  • Timer (controlla scadenze)
  • ErrorProvider (validazione)
  • StatusStrip (info stato)
✅ Requisiti Tecnici

  • Minimo 3 form separati
  • Classi custom (Libro, Prestito)
  • Validazione input completa
  • Gestione errori (try-catch)
  • Commenti nel codice

⏱️ Tempo Stimato

Questo progetto richiede 2-3 settimane di lavoro. Prenditi il tempo necessario
per farlo bene! Puoi procedere a step incrementali.

🏗️ Architettura dell’Applicazione

📐 Struttura Form

🔐 FormLogin

Scopo: Autenticazione utente
Controlli: TextBox username/password, Button login
Funzionalità: Verifica credenziali, apre FormPrincipale se OK

🏠 FormPrincipale (con TabControl)

Scopo: Dashboard principale con 4 schede
Schede:

  • 📚 Catalogo: DataGridView con tutti i libri, pulsanti Aggiungi/Modifica/Elimina
  • 📤 Prestiti: Gestisci prestiti attivi, controllo scadenze
  • 🔍 Ricerca: TextBox ricerca, filtri per categoria/autore
  • 📊 Statistiche: Grafici e numeri (libri totali, prestiti attivi, top autori)

Menu: File (Salva, Carica, Esci) | Modifica (Aggiungi Libro) | Aiuto (Info)

📕 FormDettagliLibro

Scopo: Aggiungi/Modifica libro
Controlli: TextBox (titolo, autore), NumericUpDown (anno, pagine),
ComboBox (categoria), CheckBox (disponibile)
Validazione: ErrorProvider per tutti i campi

📦 Classi per i Dati

Libro.cs
using System;

namespace GestionaleBiblioteca
{
    // Classe che rappresenta un libro
    public class Libro
    {
        public int Id { get; set; }
        public string Titolo { get; set; }
        public string Autore { get; set; }
        public int Anno { get; set; }
        public int Pagine { get; set; }
        public string Categoria { get; set; }  // Es: Fantasy, Thriller, Saggio
        public bool Disponibile { get; set; }
        public DateTime? DataAggiunta { get; set; }
        
        // Proprietà calcolata per CSV
        public string CsvFormat => 
            $"{Id};{Titolo};{Autore};{Anno};{Pagine};{Categoria};{Disponibile};{DataAggiunta:yyyy-MM-dd}";
        
        // Costruttore vuoto
        public Libro() 
        {
            Disponibile = true;
            DataAggiunta = DateTime.Now;
        }
        
        // Costruttore con parametri
        public Libro(int id, string titolo, string autore, int anno, int pagine, string categoria)
        {
            Id = id;
            Titolo = titolo;
            Autore = autore;
            Anno = anno;
            Pagine = pagine;
            Categoria = categoria;
            Disponibile = true;
            DataAggiunta = DateTime.Now;
        }
        
        // Parsing da riga CSV
        public static Libro FromCsv(string csvLine)
        {
            string[] parts = csvLine.Split(';');
            
            return new Libro
            {
                Id = int.Parse(parts[0]),
                Titolo = parts[1],
                Autore = parts[2],
                Anno = int.Parse(parts[3]),
                Pagine = int.Parse(parts[4]),
                Categoria = parts[5],
                Disponibile = bool.Parse(parts[6]),
                DataAggiunta = DateTime.Parse(parts[7])
            };
        }
        
        public override string ToString()
        {
            return $"{Titolo} - {Autore} ({Anno})";
        }
    }
}

Prestito.cs
using System;

namespace GestionaleBiblioteca
{
    public class Prestito
    {
        public int Id { get; set; }
        public int IdLibro { get; set; }
        public string NomeUtente { get; set; }
        public DateTime DataPrestito { get; set; }
        public DateTime DataScadenza { get; set; }
        public DateTime? DataRestituzione { get; set; }
        
        // Proprietà calcolate
        public bool Attivo => DataRestituzione == null;
        public bool Scaduto => Attivo && DateTime.Now > DataScadenza;
        public int GiorniRimanenti => (DataScadenza - DateTime.Now).Days;
        
        public string CsvFormat => 
            $"{Id};{IdLibro};{NomeUtente};{DataPrestito:yyyy-MM-dd};{DataScadenza:yyyy-MM-dd};{DataRestituzione?.ToString("yyyy-MM-dd") ?? ""}";
        
        // Costruttore standard: prestito di 14 giorni
        public Prestito(int id, int idLibro, string nomeUtente)
        {
            Id = id;
            IdLibro = idLibro;
            NomeUtente = nomeUtente;
            DataPrestito = DateTime.Now;
            DataScadenza = DateTime.Now.AddDays(14);  // 2 settimane
            DataRestituzione = null;
        }
        
        // Costruttore vuoto
        public Prestito() { }
        
        public static Prestito FromCsv(string csvLine)
        {
            string[] parts = csvLine.Split(';');
            
            return new Prestito
            {
                Id = int.Parse(parts[0]),
                IdLibro = int.Parse(parts[1]),
                NomeUtente = parts[2],
                DataPrestito = DateTime.Parse(parts[3]),
                DataScadenza = DateTime.Parse(parts[4]),
                DataRestituzione = string.IsNullOrEmpty(parts[5]) ? null : (DateTime?)DateTime.Parse(parts[5])
            };
        }
    }
}

🔐 Step 1: FormLogin

FormLogin.cs (Completo)
using System;
using System.Windows.Forms;

namespace GestionaleBiblioteca
{
    public partial class FormLogin : Form
    {
        // Credenziali (in app reale: database!)
        private const string USERNAME = "admin";
        private const string PASSWORD = "biblioteca";
        
        public FormLogin()
        {
            InitializeComponent();
            
            txt_password.PasswordChar = '●';
            this.AcceptButton = btn_login;  // INVIO = click login
            
            // Centra il form
            this.StartPosition = FormStartPosition.CenterScreen;
        }
        
        private void btn_login_Click(object sender, EventArgs e)
        {
            string user = txt_username.Text.Trim();
            string pass = txt_password.Text;
            
            if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass))
            {
                lbl_errore.Text = "❌ Compila tutti i campi!";
                lbl_errore.Visible = true;
                return;
            }
            
            if (user == USERNAME && pass == PASSWORD)
            {
                // ✅ Login OK!
                lbl_errore.Visible = false;
                
                // Apri form principale
                FormPrincipale principale = new FormPrincipale(user);
                principale.Show();
                
                // Nascondi login
                this.Hide();
                
                // Quando chiude il principale, chiudi anche questo
                principale.FormClosed += (s, args) => this.Close();
            }
            else
            {
                // ❌ Errore
                lbl_errore.Text = "❌ Credenziali errate!";
                lbl_errore.Visible = true;
                txt_password.Clear();
                txt_password.Focus();
                
                // Animazione shake
                AnimazioneErrore();
            }
        }
        
        private void AnimazioneErrore()
        {
            int posOriginale = this.Left;
            this.Left -= 10;
            System.Threading.Thread.Sleep(50);
            this.Left = posOriginale + 10;
            System.Threading.Thread.Sleep(50);
            this.Left = posOriginale;
        }
    }
}

🏠 Step 2: FormPrincipale (Struttura)

FormPrincipale.cs (Parte 1 – Setup)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace GestionaleBiblioteca
{
    public partial class FormPrincipale : Form
    {
        // ═══════════════════════════════════════════
        // VARIABILI GLOBALI
        // ═══════════════════════════════════════════
        private List libri = new List();
        private List prestiti = new List();
        private string nomeUtente;
        private const string FILE_LIBRI = "libri.csv";
        private const string FILE_PRESTITI = "prestiti.csv";
        
        // ═══════════════════════════════════════════
        // COSTRUTTORE
        // ═══════════════════════════════════════════
        public FormPrincipale(string utente)
        {
            InitializeComponent();
            
            this.nomeUtente = utente;
            this.Text = $"Gestionale Biblioteca - {utente}";
            this.WindowState = FormWindowState.Maximized;
            
            // Setup iniziale
            ImpostaDataGridView();
            CaricaDati();
            AggiornaStatistiche();
            
            // Timer per controllare scadenze ogni minuto
            timer_scadenze.Interval = 60000; // 1 minuto
            timer_scadenze.Tick += timer_scadenze_Tick;
            timer_scadenze.Start();
            
            // StatusBar
            toolStripStatusLabel.Text = $"Benvenuto, {utente}! | {libri.Count} libri nel catalogo";
        }
        
        // ═══════════════════════════════════════════
        // SETUP DATAGRIDVIEW
        // ═══════════════════════════════════════════
        private void ImpostaDataGridView()
        {
            dgv_catalogo.Columns.Clear();
            dgv_catalogo.Columns.Add("Id", "ID");
            dgv_catalogo.Columns.Add("Titolo", "Titolo");
            dgv_catalogo.Columns.Add("Autore", "Autore");
            dgv_catalogo.Columns.Add("Anno", "Anno");
            dgv_catalogo.Columns.Add("Pagine", "Pagine");
            dgv_catalogo.Columns.Add("Categoria", "Categoria");
            dgv_catalogo.Columns.Add("Disponibile", "Disponibile");
            
            dgv_catalogo.Columns["Id"].Width = 50;
            dgv_catalogo.Columns["Titolo"].Width = 250;
            dgv_catalogo.Columns["Autore"].Width = 200;
            dgv_catalogo.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            dgv_catalogo.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            dgv_catalogo.MultiSelect = false;
            dgv_catalogo.AllowUserToAddRows = false;
            
            // Stesso setup per dgv_prestiti
            dgv_prestiti.Columns.Clear();
            dgv_prestiti.Columns.Add("Id", "ID");
            dgv_prestiti.Columns.Add("Libro", "Libro");
            dgv_prestiti.Columns.Add("Utente", "Utente");
            dgv_prestiti.Columns.Add("DataPrestito", "Data Prestito");
            dgv_prestiti.Columns.Add("Scadenza", "Scadenza");
            dgv_prestiti.Columns.Add("GiorniRim", "Giorni Rim.");
            dgv_prestiti.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        }
        
        // ═══════════════════════════════════════════
        // CARICA DATI DA FILE
        // ═══════════════════════════════════════════
        private void CaricaDati()
        {
            try
            {
                // Carica libri
                if (File.Exists(FILE_LIBRI))
                {
                    string[] righe = File.ReadAllLines(FILE_LIBRI);
                    foreach (string riga in righe)
                    {
                        if (!string.IsNullOrWhiteSpace(riga))
                        {
                            libri.Add(Libro.FromCsv(riga));
                        }
                    }
                }
                else
                {
                    // Prima volta: aggiungi libri di esempio
                    AggiungiLibriDiEsempio();
                }
                
                // Carica prestiti
                if (File.Exists(FILE_PRESTITI))
                {
                    string[] righe = File.ReadAllLines(FILE_PRESTITI);
                    foreach (string riga in righe)
                    {
                        if (!string.IsNullOrWhiteSpace(riga))
                        {
                            prestiti.Add(Prestito.FromCsv(riga));
                        }
                    }
                }
                
                AggiornaDataGridView();
                AggiornaPrestiti();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Errore caricamento dati: {ex.Message}", "Errore", 
                              MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        // ═══════════════════════════════════════════
        // SALVA DATI SU FILE
        // ═══════════════════════════════════════════
        private void SalvaDati()
        {
            try
            {
                // Salva libri
                List righeLibri = new List();
                foreach (Libro libro in libri)
                {
                    righeLibri.Add(libro.CsvFormat);
                }
                File.WriteAllLines(FILE_LIBRI, righeLibri);
                
                // Salva prestiti
                List righe Prestiti = new List();
                foreach (Prestito p in prestiti)
                {
                    righe Prestiti.Add(p.CsvFormat);
                }
                File.WriteAllLines(FILE_PRESTITI, righe Prestiti);
                
                toolStripStatusLabel.Text = $"Dati salvati alle {DateTime.Now:HH:mm:ss}";
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Errore salvataggio: {ex.Message}", "Errore", 
                              MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        // ═══════════════════════════════════════════
        // AGGIORNA DATAGRIDVIEW CATALOGO
        // ═══════════════════════════════════════════
        private void AggiornaDataGridView()
        {
            dgv_catalogo.Rows.Clear();
            
            foreach (Libro libro in libri)
            {
                int rowIndex = dgv_catalogo.Rows.Add(
                    libro.Id,
                    libro.Titolo,
                    libro.Autore,
                    libro.Anno,
                    libro.Pagine,
                    libro.Categoria,
                    libro.Disponibile ? "✅ Sì" : "❌ No"
                );
                
                // Colora riga se non disponibile
                if (!libro.Disponibile)
                {
                    dgv_catalogo.Rows[rowIndex].DefaultCellStyle.BackColor = Color.LightCoral;
                }
            }
        }
        
        // ═══════════════════════════════════════════
        // LIBRI DI ESEMPIO (prima volta)
        // ═══════════════════════════════════════════
        private void AggiungiLibriDiEsempio()
        {
            libri.Add(new Libro(1, "Il Signore degli Anelli", "J.R.R. Tolkien", 1954, 1178, "Fantasy"));
            libri.Add(new Libro(2, "1984", "George Orwell", 1949, 328, "Distopico"));
            libri.Add(new Libro(3, "Harry Potter", "J.K. Rowling", 1997, 309, "Fantasy"));
            libri.Add(new Libro(4, "Dune", "Frank Herbert", 1965, 688, "Fantascienza"));
            libri.Add(new Libro(5, "Il Nome della Rosa", "Umberto Eco", 1980, 503, "Thriller"));
        }
    }
}

➕ Step 3: CRUD Libri

FormPrincipale.cs (Parte 2 – CRUD)
        // ═══════════════════════════════════════════
        // PULSANTE: Aggiungi Libro
        // ═══════════════════════════════════════════
        private void btn_aggiungi_libro_Click(object sender, EventArgs e)
        {
            FormDettagliLibro formDettagli = new FormDettagliLibro();
            
            if (formDettagli.ShowDialog() == DialogResult.OK)
            {
                // Nuovo libro da aggiungere
                Libro nuovoLibro = formDettagli.LibroCorrente;
                
                // Assegna ID automatico
                nuovoLibro.Id = libri.Count > 0 ? libri.Max(l => l.Id) + 1 : 1;
                
                // Aggiungi alla lista
                libri.Add(nuovoLibro);
                
                // Aggiorna visualizzazione
                AggiornaDataGridView();
                AggiornaStatistiche();
                
                // Salva
                SalvaDati();
                
                MessageBox.Show($"Libro '{nuovoLibro.Titolo}' aggiunto!", "Successo", 
                              MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        
        // ═══════════════════════════════════════════
        // PULSANTE: Modifica Libro
        // ═══════════════════════════════════════════
        private void btn_modifica_libro_Click(object sender, EventArgs e)
        {
            if (dgv_catalogo.SelectedRows.Count == 0)
            {
                MessageBox.Show("Seleziona un libro da modificare!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            int indice = dgv_catalogo.SelectedRows[0].Index;
            Libro libroSelezionato = libri[indice];
            
            FormDettagliLibro formDettagli = new FormDettagliLibro(libroSelezionato);
            
            if (formDettagli.ShowDialog() == DialogResult.OK)
            {
                // Aggiorna il libro
                libri[indice] = formDettagli.LibroCorrente;
                
                // Aggiorna visualizzazione
                AggiornaDataGridView();
                SalvaDati();
                
                MessageBox.Show("Libro aggiornato!", "Successo", 
                              MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        
        // ═══════════════════════════════════════════
        // PULSANTE: Elimina Libro
        // ═══════════════════════════════════════════
        private void btn_elimina_libro_Click(object sender, EventArgs e)
        {
            if (dgv_catalogo.SelectedRows.Count == 0)
            {
                MessageBox.Show("Seleziona un libro da eliminare!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            int indice = dgv_catalogo.SelectedRows[0].Index;
            Libro libro = libri[indice];
            
            // Controlla se è in prestito
            bool inPrestito = prestiti.Any(p => p.IdLibro == libro.Id && p.Attivo);
            
            if (inPrestito)
            {
                MessageBox.Show("Impossibile eliminare: libro in prestito!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            DialogResult ris = MessageBox.Show(
                $"Eliminare '{libro.Titolo}'?", 
                "Conferma",
                MessageBoxButtons.YesNo, 
                MessageBoxIcon.Question);
            
            if (ris == DialogResult.Yes)
            {
                libri.RemoveAt(indice);
                AggiornaDataGridView();
                AggiornaStatistiche();
                SalvaDati();
            }
        }
        
        // ═══════════════════════════════════════════
        // RICERCA LIBRI
        // ═══════════════════════════════════════════
        private void btn_cerca_Click(object sender, EventArgs e)
        {
            string termine = txt_ricerca.Text.Trim().ToLower();
            
            if (string.IsNullOrWhiteSpace(termine))
            {
                // Mostra tutti
                AggiornaDataGridView();
                return;
            }
            
            // Filtra libri
            var risultati = libri.Where(l =>
                l.Titolo.ToLower().Contains(termine) ||
                l.Autore.ToLower().Contains(termine) ||
                l.Categoria.ToLower().Contains(termine)
            ).ToList();
            
            // Mostra risultati
            dgv_catalogo.Rows.Clear();
            foreach (Libro libro in risultati)
            {
                dgv_catalogo.Rows.Add(
                    libro.Id, libro.Titolo, libro.Autore, 
                    libro.Anno, libro.Pagine, libro.Categoria,
                    libro.Disponibile ? "✅ Sì" : "❌ No"
                );
            }
            
            toolStripStatusLabel.Text = $"Trovati {risultati.Count} risultati per '{termine}'";
        }

📤 Step 4: Gestione Prestiti

FormPrincipale.cs (Parte 3 – Prestiti)
        // ═══════════════════════════════════════════
        // PULSANTE: Nuovo Prestito
        // ═══════════════════════════════════════════
        private void btn_nuovo_prestito_Click(object sender, EventArgs e)
        {
            if (dgv_catalogo.SelectedRows.Count == 0)
            {
                MessageBox.Show("Seleziona un libro!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            int indice = dgv_catalogo.SelectedRows[0].Index;
            Libro libro = libri[indice];
            
            if (!libro.Disponibile)
            {
                MessageBox.Show("Libro già in prestito!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            // Chiedi nome utente
            string nomeUtentePrestito = Microsoft.VisualBasic.Interaction.InputBox(
                "Inserisci il nome della persona che prende in prestito il libro:",
                "Nuovo Prestito",
                ""
            );
            
            if (string.IsNullOrWhiteSpace(nomeUtentePrestito))
                return;
            
            // Crea prestito
            int idPrestito = prestiti.Count > 0 ? prestiti.Max(p => p.Id) + 1 : 1;
            Prestito prestito = new Prestito(idPrestito, libro.Id, nomeUtentePrestito);
            
            prestiti.Add(prestito);
            
            // Segna libro come non disponibile
            libro.Disponibile = false;
            
            // Aggiorna
            AggiornaDataGridView();
            AggiornaPrestiti();
            AggiornaStatistiche();
            SalvaDati();
            
            MessageBox.Show(
                $"Prestito registrato!\n\n" +
                $"Libro: {libro.Titolo}\n" +
                $"Utente: {nomeUtentePrestito}\n" +
                $"Scadenza: {prestito.DataScadenza:dd/MM/yyyy}", 
                "Prestito Creato", 
                MessageBoxButtons.OK, 
                MessageBoxIcon.Information);
        }
        
        // ═══════════════════════════════════════════
        // PULSANTE: Restituisci
        // ═══════════════════════════════════════════
        private void btn_restituisci_Click(object sender, EventArgs e)
        {
            if (dgv_prestiti.SelectedRows.Count == 0)
            {
                MessageBox.Show("Seleziona un prestito!", "Attenzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            int indicePrestito = dgv_prestiti.SelectedRows[0].Index;
            var prestitiAttivi = prestiti.Where(p => p.Attivo).ToList();
            Prestito prestito = prestitiAttivi[indicePrestito];
            
            // Registra restituzione
            prestito.DataRestituzione = DateTime.Now;
            
            // Trova il libro e rendilo disponibile
            Libro libro = libri.FirstOrDefault(l => l.Id == prestito.IdLibro);
            if (libro != null)
            {
                libro.Disponibile = true;
            }
            
            // Aggiorna
            AggiornaDataGridView();
            AggiornaPrestiti();
            AggiornaStatistiche();
            SalvaDati();
            
            // Verifica se in ritardo
            if (prestito.Scaduto)
            {
                int giorniRitardo = (DateTime.Now - prestito.DataScadenza).Days;
                MessageBox.Show(
                    $"Libro restituito in RITARDO di {giorniRitardo} giorni!", 
                    "Restituzione", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Warning);
            }
            else
            {
                MessageBox.Show("Libro restituito correttamente!", "Restituzione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        
        // ═══════════════════════════════════════════
        // AGGIORNA TABELLA PRESTITI
        // ═══════════════════════════════════════════
        private void AggiornaPrestiti()
        {
            dgv_prestiti.Rows.Clear();
            
            var prestitiAttivi = prestiti.Where(p => p.Attivo).OrderBy(p => p.DataScadenza);
            
            foreach (Prestito p in prestitiAttivi)
            {
                Libro libro = libri.FirstOrDefault(l => l.Id == p.IdLibro);
                string titoloLibro = libro != null ? libro.Titolo : "Sconosciuto";
                
                int rowIndex = dgv_prestiti.Rows.Add(
                    p.Id,
                    titoloLibro,
                    p.NomeUtente,
                    p.DataPrestito.ToString("dd/MM/yyyy"),
                    p.DataScadenza.ToString("dd/MM/yyyy"),
                    p.GiorniRimanenti
                );
                
                // Colora se scaduto
                if (p.Scaduto)
                {
                    dgv_prestiti.Rows[rowIndex].DefaultCellStyle.BackColor = Color.LightCoral;
                }
                else if (p.GiorniRimanenti <= 3)
                {
                    dgv_prestiti.Rows[rowIndex].DefaultCellStyle.BackColor = Color.LightYellow;
                }
            }
        }
        
        // ═══════════════════════════════════════════
        // TIMER: Controlla Scadenze
        // ═══════════════════════════════════════════
        private void timer_scadenze_Tick(object sender, EventArgs e)
        {
            int prestitiScaduti = prestiti.Count(p => p.Scaduto);
            
            if (prestitiScaduti > 0)
            {
                toolStripStatusLabel.Text = $"⚠️ {prestitiScaduti} prestiti scaduti!";
                toolStripStatusLabel.ForeColor = Color.Red;
            }
        }

📊 Step 5: Statistiche

FormPrincipale.cs (Parte 4 – Statistiche)
        // ═══════════════════════════════════════════
        // AGGIORNA STATISTICHE
        // ═══════════════════════════════════════════
        private void AggiornaStatistiche()
        {
            // Totale libri
            lbl_totale_libri.Text = $"Totale Libri: {libri.Count}";
            
            // Libri disponibili
            int disponibili = libri.Count(l => l.Disponibile);
            lbl_disponibili.Text = $"Disponibili: {disponibili}";
            
            // Prestiti attivi
            int prestitiAttivi = prestiti.Count(p => p.Attivo);
            lbl_prestiti_attivi.Text = $"Prestiti Attivi: {prestitiAttivi}";
            
            // Autore più presente
            if (libri.Count > 0)
            {
                var autoreTop = libri.GroupBy(l => l.Autore)
                                    .OrderByDescending(g => g.Count())
                                    .First();
                
                lbl_autore_top.Text = $"Autore Top: {autoreTop.Key} ({autoreTop.Count()} libri)";
            }
            
            // Categoria più presente
            if (libri.Count > 0)
            {
                var categoriaTop = libri.GroupBy(l => l.Categoria)
                                       .OrderByDescending(g => g.Count())
                                       .First();
                
                lbl_categoria_top.Text = $"Categoria Top: {categoriaTop.Key} ({categoriaTop.Count()} libri)";
            }
            
            // Anno medio
            if (libri.Count > 0)
            {
                int annoMedio = (int)libri.Average(l => l.Anno);
                lbl_anno_medio.Text = $"Anno Medio: {annoMedio}";
            }
        }

📕 Step 6: FormDettagliLibro

FormDettagliLibro.cs (Form Modale)
using System;
using System.Windows.Forms;

namespace GestionaleBiblioteca
{
    public partial class FormDettagliLibro : Form
    {
        public Libro LibroCorrente { get; private set; }
        
        private bool modalitaModifica = false;
        
        // Costruttore per NUOVO libro
        public FormDettagliLibro()
        {
            InitializeComponent();
            
            this.Text = "Nuovo Libro";
            LibroCorrente = new Libro();
            modalitaModifica = false;
            
            // Popola categorie
            cmb_categoria.Items.AddRange(new string[] 
            { 
                "Fantasy", "Fantascienza", "Thriller", 
                "Saggio", "Biografia", "Storia", "Romanzo" 
            });
            cmb_categoria.SelectedIndex = 0;
        }
        
        // Costruttore per MODIFICA libro esistente
        public FormDettagliLibro(Libro libro) : this()
        {
            this.Text = "Modifica Libro";
            LibroCorrente = libro;
            modalitaModifica = true;
            
            // Carica dati esistenti
            txt_titolo.Text = libro.Titolo;
            txt_autore.Text = libro.Autore;
            num_anno.Value = libro.Anno;
            num_pagine.Value = libro.Pagine;
            cmb_categoria.SelectedItem = libro.Categoria;
        }
        
        // ═══════════════════════════════════════════
        // PULSANTE: Salva
        // ═══════════════════════════════════════════
        private void btn_salva_Click(object sender, EventArgs e)
        {
            // Validazione
            bool valido = true;
            errorProvider.Clear();
            
            if (string.IsNullOrWhiteSpace(txt_titolo.Text))
            {
                errorProvider.SetError(txt_titolo, "Titolo obbligatorio");
                valido = false;
            }
            
            if (string.IsNullOrWhiteSpace(txt_autore.Text))
            {
                errorProvider.SetError(txt_autore, "Autore obbligatorio");
                valido = false;
            }
            
            if (num_anno.Value < 1000 || num_anno.Value > DateTime.Now.Year)
            {
                errorProvider.SetError(num_anno, "Anno non valido");
                valido = false;
            }
            
            if (num_pagine.Value < 1)
            {
                errorProvider.SetError(num_pagine, "Pagine devono essere > 0");
                valido = false;
            }
            
            if (!valido)
            {
                MessageBox.Show("Correggi gli errori!", "Validazione", 
                              MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            // Salva dati nel libro
            LibroCorrente.Titolo = txt_titolo.Text.Trim();
            LibroCorrente.Autore = txt_autore.Text.Trim();
            LibroCorrente.Anno = (int)num_anno.Value;
            LibroCorrente.Pagine = (int)num_pagine.Value;
            LibroCorrente.Categoria = cmb_categoria.SelectedItem.ToString();
            
            // Se non in modifica, è disponibile di default
            if (!modalitaModifica)
            {
                LibroCorrente.Disponibile = true;
                LibroCorrente.DataAggiunta = DateTime.Now;
            }
            
            // Chiudi con OK
            this.DialogResult = DialogResult.OK;
            this.Close();
        }
        
        // ═══════════════════════════════════════════
        // PULSANTE: Annulla
        // ═══════════════════════════════════════════
        private void btn_annulla_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }
    }
}

✅ Checklist Finale

📋 Verifica di Aver Implementato Tutto

🔐 Login:

  • ☐ Form login con validazione
  • ☐ Apre form principale se credenziali OK
  • ☐ Animazione errore

📚 Catalogo Libri:

  • ☐ DataGridView con tutti i libri
  • ☐ Aggiungi libro (form modale)
  • ☐ Modifica libro
  • ☐ Elimina libro (con conferma)
  • ☐ Colore diverso per libri non disponibili

📤 Prestiti:

  • ☐ Crea nuovo prestito
  • ☐ Visualizza prestiti attivi
  • ☐ Restituisci libro
  • ☐ Evidenzia prestiti scaduti
  • ☐ Timer controllo scadenze

🔍 Ricerca:

  • ☐ Ricerca per titolo/autore/categoria
  • ☐ Mostra risultati filtrati

📊 Statistiche:

  • ☐ Totale libri
  • ☐ Libri disponibili
  • ☐ Prestiti attivi
  • ☐ Autore/categoria top

💾 Persistenza Dati:

  • ☐ Salvataggio su file CSV
  • ☐ Caricamento all’avvio
  • ☐ Gestione errori (try-catch)

🎨 UI/UX:

  • ☐ StatusStrip con info
  • ☐ ErrorProvider validazione
  • ☐ MenuStrip (File, Modifica, Aiuto)
  • ☐ ToolTip su controlli
  • ☐ Icone/emoji per rendere accattivante

🚀 Possibili Migliorie (Bonus)

💡 Se Vuoi Andare Oltre:

  1. Stampa Report: Genera PDF o documento Word con statistiche
  2. Grafici: Usa Chart per mostrare grafici a barre/torta
  3. Export/Import Excel: Esporta catalogo in Excel
  4. Foto Copertine: Aggiungi PictureBox per mostrare copertine
  5. Sistema Voti: Gli utenti possono votare i libri (1-5 stelle)
  6. Notifiche Email: Invia promemoria scadenze via email
  7. Multi-Utente: Login diversi con permessi diversi (admin/utente)
  8. Database Vero: Passa da CSV a SQLite o SQL Server
  9. Temi: Modalità chiara/scura
  10. Storico: Salva cronologia di tutti i prestiti passati

🎉 CONGRATULAZIONI!

Hai completato il percorso Windows Forms! Ora sai creare applicazioni
desktop professionali con interfacce grafiche complete. Questo progetto
dimostra che hai padronanza di:

✅ Tutti i controlli principali
✅ Gestione eventi
✅ Architettura multi-finestra
✅ Persistenza dati
✅ Validazione professionale
✅ LINQ e collezioni
✅ Problem solving

Sei pronto per il prossimo livello: la programmazione teorica! 🚀

Torna in alto