C# cu Windows Forms e una dintre cele mai directe căi spre o aplicație desktop care arată a aplicație: desenezi fereastra cu mouse-ul în Visual Studio, apoi scrii codul care rulează la fiecare click. Sintaxa e apropiată de C++, deci trecerea de la ce ai lucrat la clasă e mai scurtă decât pare.
Pagina asta e despre o aplicație desktop cu interfață desenată în designer, legată de o bază de date cu cel puțin trei tabele. Dacă preferi să scrii și interfața în cod, fără designer și fără Visual Studio, e proiectul în Python. Dacă vrei baza de date construită vizual, fără cod, e proiectul în Access. Dacă vrei aplicație în browser, e proiectul în PHP și MySQL.
Dacă încă alegi între tehnologii, pornește de la cum îți alegi tema de proiect. Cadrul general al examenului e în ghidul atestatului.
Pentru cine e potrivit
Un proiect în C# cu Windows Forms merge dacă:
- vrei o fereastră cu meniuri, tabele și formulare fără să scrii layout-ul de mână;
- ai lucrat în C++ și vrei un limbaj apropiat, în care diferența o fac clasele și evenimentele;
- tema are entități care se leagă — pacienți și programări, elevi și note, produse și stocuri.
Nu merge dacă nu ai acces constant la un calculator cu Windows și Visual Studio — Windows Forms nu rulează pe macOS sau Linux. Nu merge nici pentru un site de prezentare; acolo e proiectul în HTML, CSS și JavaScript.
Capcana numărul unu: o fereastră frumoasă fără date nu e o aplicație
Designerul face ușor să ai zece butoane și trei taburi în prima seară. Asta nu e un proiect, ci o machetă.
| Nivel | Ce conține | Cum e primit |
|---|---|---|
| Ferestre cu butoane, fără date salvate | Machetă | Insuficient |
| Un formular care salvează într-un fișier text | Interfață, fără model de date | Minim acceptabil |
Formulare + bază de date cu 3–4 tabele legate, DataGridView, interogări cu JOIN și agregare |
Aplicație reală | Ținta |
| Plus căutare, editare, validări, rapoarte tipăribile | Aplicație închegată | Peste așteptări |
Ținta e rândul al treilea. Peste el treci doar dacă îți rămâne timp după documentație.
Prima decizie: .NET sau .NET Framework
La New Project, Visual Studio îți oferă două șabloane cu nume aproape identice:
| Șablon | Ce e | Când îl alegi |
|---|---|---|
| Windows Forms App | .NET modern (8, 9, 10) | Implicit, dacă profesorul nu cere altceva |
| Windows Forms App (.NET Framework) | .NET Framework 4.x, versiunea veche | Dacă laboratorul liceului are un Visual Studio vechi sau așa ați lucrat la clasă |
Diferența contează pentru cod copiat de pe internet. Proiectele .NET Framework compilează implicit
cu C# 7.3, iar construcții moderne folosite peste tot în exemplele noi nu există acolo. Un
using var con = ... dă, pe C# 7.3:
error CS8370: Feature 'using declarations' is not available in C# 7.3.
Alege o dată, la început, și scrie în documentație ce versiune ai folosit.
Ce bază de date legi
| Bază de date | Cum o legi | Pe calculatorul comisiei |
|---|---|---|
| SQLite | pachetul NuGet Microsoft.Data.Sqlite |
un fișier lângă aplicație — nimic de instalat |
Access (.accdb) |
OleDbConnection cu Microsoft.ACE.OLEDB |
cere driverul Access, pe același număr de biți ca aplicația |
| SQL Server Express / LocalDB | SqlConnection |
cere SQL Server instalat |
SQLite e varianta cu cele mai puține surprize la examen și e cea folosită mai jos. Dacă profesorul cere Access, citește capcana cu biții de mai jos înainte să scrii prima linie.
Codul: o clasă pentru bază, evenimentele în formular
Exemplul e un cabinet medical: pacienți, medici, programări. Accesul la date stă într-o clasă separată, nu împrăștiat prin butoane:
// Baza.cs
using Microsoft.Data.Sqlite;
static class Baza
{
public const string Conexiune = "Data Source=cabinet.db";
public static SqliteConnection Deschide()
{
var con = new SqliteConnection(Conexiune);
con.Open();
return con;
}
public static void Initializeaza()
{
using var con = Deschide();
using var cmd = con.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS pacienti(
id_pacient INTEGER PRIMARY KEY,
nume TEXT NOT NULL,
telefon TEXT);
CREATE TABLE IF NOT EXISTS medici(
id_medic INTEGER PRIMARY KEY,
nume TEXT NOT NULL,
specializare TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS programari(
id_programare INTEGER PRIMARY KEY,
id_pacient INTEGER NOT NULL REFERENCES pacienti(id_pacient),
id_medic INTEGER NOT NULL REFERENCES medici(id_medic),
data_ora TEXT NOT NULL);";
cmd.ExecuteNonQuery();
}
}
Microsoft.Data.Sqlite activează cheile străine implicit: o programare pentru un pacient
inexistent e refuzată cu FOREIGN KEY constraint failed, fără nicio setare în plus. Nu toate
bibliotecile fac asta — în Python, de exemplu, trebuie activate manual. Dacă folosești altă
bibliotecă, verifică rulând PRAGMA foreign_keys;, care trebuie să întoarcă 1.
În formular, butonul și tabelul vizual:
// Form1.cs — în capul fișierului: using System.Data;
private void btnAdauga_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtNume.Text))
{
MessageBox.Show("Numele pacientului e obligatoriu.", "Date lipsă");
return;
}
using var con = Baza.Deschide();
using var cmd = con.CreateCommand();
cmd.CommandText = "INSERT INTO pacienti(nume, telefon) VALUES (@nume, @telefon)";
cmd.Parameters.AddWithValue("@nume", txtNume.Text.Trim());
cmd.Parameters.AddWithValue("@telefon", txtTelefon.Text.Trim());
cmd.ExecuteNonQuery();
txtNume.Clear();
txtTelefon.Clear();
Reincarca();
}
private void Reincarca()
{
using var con = Baza.Deschide();
using var cmd = con.CreateCommand();
cmd.CommandText = "SELECT id_pacient, nume, telefon FROM pacienti ORDER BY nume";
using var cititor = cmd.ExecuteReader();
var tabel = new DataTable();
tabel.Load(cititor);
gridPacienti.DataSource = tabel;
}
Ce trebuie să poți explica din codul ăsta:
using varînchide conexiunea automat la ieșirea din metodă, chiar dacă apare o eroare. O conexiune deschisă și uitată ține fișierul bazei blocat.@numeșiParameters.AddWithValue— valoarea din căsuța de text nu devine niciodată parte din comanda SQL. Vezi secțiunea despre injecție, mai jos.DataTable+DataSource—DataGridViewîși construiește singur coloanele din rezultatul interogării. Nu adaugi rânduri unul câte unul.Reincarca()după fiecare modificare — ce vezi în tabel e întotdeauna ce e în bază.
Baza.Initializeaza() se apelează o dată, în constructorul formularului, după
InitializeComponent(). Aplicația își creează singură baza la prima pornire.
Designerul: două fișiere, unul nu se atinge
Fiecare formular are două fișiere: Form1.cs, unde scrii tu, și Form1.Designer.cs, pe care îl
scrie Visual Studio când tragi controale pe fereastră.
- Nu edita manual
Form1.Designer.cs. E regenerat de designer; modificările tale fie dispar, fie strică fereastra. - Redenumește controalele înainte să dai dublu-click pe ele. Dublu-click pe
button1creeazăbutton1_Click. Peste o lună aibutton1_Click,button7_ClickșitextBox4și nu mai știi care e care — iar comisia vede asta în cod.btnAdauga,txtNume,gridPacientise explică singure. - Nu șterge metoda unui eveniment direct din cod. Designerul încă are legătura spre ea în
Form1.Designer.cs, iar proiectul nu mai compilează — eroarea (de exempluCS0103: The name 'Form1_Load' does not exist) arată spre un fișier pe care nu l-ai atins. Șterge evenimentul din fereastra Properties → Events, nu din cod.
Interogarea care se notează
SELECT m.nume, m.specializare, COUNT(p.id_programare) AS programari
FROM medici AS m
LEFT JOIN programari AS p ON p.id_medic = m.id_medic
GROUP BY m.id_medic
ORDER BY programari DESC;
LEFT JOIN e alegerea care se justifică: un medic fără nicio programare apare în raport cu 0. Cu
JOIN simplu ar dispărea din listă. Diferența asta, spusă în cuvintele tale, e exact motivarea
teoretică pe care o cere proba. Rezultatul îl pui într-un DataGridView separat, exact ca la
Reincarca(). Tehnicile sunt aceleași ca la
interogările SQL de la proba practică.
Datele utilizatorului nu intră niciodată direct în SQL
Pe aceeași bază cu doi pacienți, cu textul ' OR '1'='1 scris în câmpul de căutare:
cmd.CommandText = "SELECT COUNT(*) FROM pacienti WHERE nume = '" + txtCauta.Text + "'";
// -> 2: condiția a devenit adevărată pentru toate rândurile
cmd.CommandText = "SELECT COUNT(*) FROM pacienti WHERE nume = @nume";
cmd.Parameters.AddWithValue("@nume", txtCauta.Text);
// -> 0: textul e căutat ca text, nu executat
Concatenarea cu + transformă textul introdus în cod SQL — SQL injection. Multe tutoriale vechi
o folosesc. Dacă ai copiat un astfel de exemplu, schimbă-l înainte de prezentare.
Capcana cu Access: 32 sau 64 de biți
Dacă lucrezi cu o bază .accdb prin OleDbConnection, aplicația și driverul Access trebuie să aibă
același număr de biți. Când nu au, conexiunea pică la Open() cu:
The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.
Mesajul pare să spună că driverul lipsește. De cele mai multe ori driverul e acolo, dar pe 64 de
biți, iar aplicația rulează pe 32 — sau invers. Se rezolvă din Properties → Build → Platform
target, alegând explicit x64 sau x86 ca să se potrivească cu Office-ul instalat. Pe
calculatorul comisiei, Office-ul poate fi pe alt număr de biți decât al tău — încă un motiv să
testezi acolo din timp, sau să alegi SQLite.
Cum ajunge aplicația pe calculatorul comisiei
Ai două variante de executabil, ambele din Build → Publish sau din linia de comandă:
| Variantă | Mărime (aplicația de pe pagina asta) | Pe calculatorul comisiei |
|---|---|---|
| Dependentă de runtime | ~2,4 MB, un folder | cere .NET Desktop Runtime, aceeași versiune majoră ca proiectul |
| Self-contained, un singur fișier | ~118 MB, un singur .exe |
pornește pe orice Windows pe 64 de biți, fără nimic instalat |
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
Pentru examen, varianta self-contained e cea sigură: cei 118 MB costă mai puțin decât un
calculator pe care lipsește runtime-ul. Baza cabinet.db se creează în folderul din care pornești
aplicația; pune-o cu date de test lângă exe și copiază folderul întreg.
Testează pe alt calculator cu cel puțin o săptămână înainte. Adu o copie pe stick, una în cloud, și soluția Visual Studio întreagă, separat.
Planul pe luni
Termenul de depunere e 1 mai, cu referatul profesorului îndrumător. Înapoi de la data aia:
| Când | Ce faci |
|---|---|
| Decembrie | Alegi tema și șablonul (.NET sau .NET Framework). Entitățile și relațiile pe hârtie |
| Ianuarie | Baza.cs: schema, 20–30 de înregistrări de test pe tabel |
| Februarie | Formularul principal: adăugare, listare în DataGridView, ștergere |
| Martie | Restul formularelor, căutarea, rapoartele cu JOIN și GROUP BY |
| Aprilie | Executabilul testat pe alt PC, documentația, repetiția prezentării |
| 1 mai | Depunere |
Ce te întreabă comisia
„Ce e un eveniment și unde e legat butonul de funcția lui?”
Un eveniment e o acțiune a utilizatorului — click, tastă, încărcarea ferestrei — la care formularul
răspunde apelând o metodă. Legătura e în Form1.Designer.cs, scrisă de designer. Arată-le-o.
„Ce face InitializeComponent()?”
Construiește controalele desenate în designer: le creează, le poziționează și le leagă
evenimentele. De aceea codul tău care folosește controalele vine după el.
„De ce ai pus accesul la date într-o clasă separată?” Ca butoanele să nu știe nimic despre SQL, iar schimbarea bazei de date să se facă într-un singur loc.
„Ce se întâmplă dacă încerc o programare pentru un pacient care nu există?” Baza o refuză, prin cheia străină. Arată-le pe loc.
„De ce LEFT JOIN aici?”
Ca medicii fără programări să apară cu zero, nu să dispară din raport.
„Ce ai face altfel dacă ai lua-o de la capăt?” O limitare reală, spusă concret: fără autentificare, căutare doar după nume, un singur utilizator.
Cum decurge proba și ce înseamnă motivarea teoretică: prezentarea proiectului în fața comisiei.
Greșeli frecvente
- Machetă fără date. Ferestre frumoase care nu salvează nimic.
button1,textBox4. Nume implicite pe care nu le mai poți explica.- SQL construit cu
+. Prima întrebare despre securitate și n-ai răspuns. - Conexiuni deschise și neînchise. Fișierul bazei rămâne blocat; folosește
using. - Cod editat în
Form1.Designer.cs. Dispare la următoarea modificare din designer. - Metoda unui eveniment ștearsă din cod. Proiectul nu mai compilează.
- Cod copiat pentru .NET modern într-un proiect .NET Framework.
CS8370la prima compilare. - Access pe alt număr de biți.
provider is not registered, chiar dacă driverul e instalat. - Executabil dependent de runtime, dus pe un calculator fără runtime. Aplicația nu pornește.
- Bază aproape goală. Trei înregistrări nu produc niciun raport convingător.
Ce livrezi
- Aplicația — executabilul, baza cu date de test și soluția Visual Studio cu sursele;
- Documentația — schema cu relațiile, capturi ale formularelor, explicația claselor și a interogărilor principale;
- Prezentarea — maximum 30 de minute, cu aplicația rulând.
Pentru partea de SQL, teoria e în baze de date și SQL și în interogări SQL, cu probleme rezolvate din examenele reale.
Întrebări frecvente
Se acceptă C# la proiectul de atestat?
Metodologia nu impune un limbaj pentru proiect — tehnologia o stabilește catedra liceului tău. Întreabă profesorul îndrumător înainte să începi și justifică alegerea în documentație.
Visual Studio e gratuit?
Ediția Community e gratuită pentru elevi și pentru proiecte personale. La instalare bifezi componenta pentru dezvoltare desktop .NET.
Windows Forms sau WPF?
Pentru atestat, Windows Forms: designerul e mai simplu și ajungi mai repede la partea care se notează, adică datele. WPF e mai flexibil, dar adaugă XAML, încă un limbaj de explicat.
Ce bază de date să folosesc cu C#?
SQLite, prin Microsoft.Data.Sqlite, dacă nu ți se impune altceva: e un fișier lângă aplicație și
nu cere nimic instalat pe calculatorul comisiei. Access merge, dar ai grijă la numărul de biți.
De ce primesc „provider is not registered on the local machine”?
Aplicația și driverul Access au număr diferit de biți. Setează Platform target pe x64 sau x86
în funcție de Office-ul instalat.
De ce codul copiat de pe internet nu compilează?
Cel mai des, pentru că proiectul e .NET Framework, care folosește C# 7.3, iar exemplul e scris
pentru .NET modern. Eroarea tipică e CS8370.
Aplicația pornește pe calculatorul meu, dar nu pe al comisiei. De ce?
Probabil ai publicat varianta dependentă de runtime, iar acolo lipsește .NET Desktop Runtime. Publică varianta self-contained, un singur fișier.
Se poate face proiectul în echipă?
Da, în echipă de 2–3 elevi, în funcție de complexitate. Împărțirea naturală: unul pe Baza.cs și
rapoarte, altul pe formulare — fiecare trebuie să poată explica și partea celuilalt.
De unde vin informațiile de aici
Cerința ca proiectul să fie un produs software însoțit de prezentarea și motivarea teoretică, termenul de depunere de 1 mai, referatul profesorului îndrumător și posibilitatea lucrului în echipă de 2–3 elevi sunt din Metodologia de organizare și desfășurare a examenului de atestare a competențelor profesionale ale absolvenților claselor de matematică-informatică și matematică-informatică, intensiv informatică, aprobată prin OMECI nr. 4.843/2009 și modificată prin OM nr. 3.009/2021.
Limbajul, baza de date și nivelul tehnic așteptat nu sunt prevăzute în metodologie — se
stabilesc de catedra de specialitate a liceului tău. Codul de pe pagina asta a fost compilat și
rulat pe .NET 10 cu Microsoft.Data.Sqlite 10.0, iar comportamentele descrise (cheile străine
active implicit, injecția prin concatenare, eroarea CS8370 pe C# 7.3, eroarea Access pe un proces
cu alt număr de biți, mărimile executabilelor) au fost verificate pe același cod. Verifică
întotdeauna cerința profesorului îndrumător.