Vai al contenuto

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<Libro> libri = new List<Libro>();
        private List<Prestito> prestiti = new List<Prestito>();
        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<string> righeLibri = new List<string>();
                foreach (Libro libro in libri)
                {
                    righeLibri.Add(libro.CsvFormat);
                }
                File.WriteAllLines(FILE_LIBRI, righeLibri);

                // Salva prestiti
                List<string> righe Prestiti = new List<string>();
                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! πŸš€

πŸ“š Teoria collegata (percorso C# – il linguaggio, esempi in console): Programmazione a oggetti: i fondamenti, Lettura e scrittura dei file e Progetti ed esercizi.