Aplikasi Nilai Siswa VB.NET: Rekap Nilai, Predikat dan Ranking Kelas

Aplikasi nilai siswa VB.NET dengan rekap nilai, predikat, dan ranking kelas

Setiap akhir semester, meja guru penuh dengan daftar nilai tugas, UTS, dan UAS. Semua angka itu kemudian harus berubah menjadi nilai akhir, predikat, status tuntas, dan ranking kelas. Kalau masih memakai kalkulator dan buku leger, satu kelas saja bisa menyita satu malam. Karena itu, tutorial ini mengajak Anda membangun aplikasi nilai siswa VB.NET yang mengerjakan seluruh hitungan tersebut secara otomatis, mulai dari bobot nilai dan KKM sampai cetak rekap A4.

Aplikasi ini bernama SC Nilai Siswa. Project-nya memakai Windows Forms .NET 8 tanpa database dan tanpa paket NuGet, karena seluruh data tersimpan sebagai file JSON di samping file .exe. Source code lengkapnya gratis dan berlisensi MIT, jadi Anda bisa langsung download SC Nilai Siswa VB.NET di sharecode.id lalu mencocokkannya dengan setiap langkah di bawah.

Tampilan utama aplikasi nilai siswa VB.NET dengan tabel nilai, predikat, ranking, dan kartu statistik kelas
Layar utama SC Nilai Siswa: input nilai di kiri, statistik kelas dan tabel rekap di kanan.

Apa yang Akan Kita Bangun

Sebelum menulis kode, mari lihat dulu hasil akhirnya. Dengan begitu, Anda tahu arah setiap class dan form yang akan kita buat nanti.

  • Input nilai Tugas, UTS, dan UAS (0–100, satu desimal) per siswa dengan NIS unik.
  • Nilai akhir otomatis berdasarkan bobot, lengkap dengan pratinjau langsung saat guru mengetik angka.
  • Predikat A/B/C/D serta keterangan Tuntas atau Belum Tuntas berwarna sesuai KKM.
  • Ranking kelas otomatis, sehingga nilai yang sama mendapat peringkat yang sama (1, 2, 2, 4).
  • Kartu statistik berisi jumlah siswa, rata-rata kelas, nilai tertinggi, nilai terendah, dan persentase tuntas.
  • Pencarian berdasarkan NIS atau nama, ekspor CSV untuk Excel, serta cetak rekap A4 lewat Print Preview.

Persiapan: Visual Studio 2022 dan .NET 8

Pertama, pasang Visual Studio 2022 versi 17.8 atau lebih baru (edisi Community gratis) dengan workload .NET desktop development. Versi 17.8 menjadi batas minimum karena dukungan .NET 8 baru masuk pada rilis tersebut. Sebaliknya, Visual Studio 2019 sama sekali tidak bisa membuka project .NET 8. Jika Anda lebih suka terminal, cukup pasang .NET 8 SDK lalu jalankan perintah dotnet.

Membuat Project WinForms VB dari Nol

  1. Buka Visual Studio 2022, lalu pilih Create a new project.
  2. Ketik “Windows Forms App” pada kotak pencarian, kemudian pilih template berbahasa Visual Basic (bukan C#, juga bukan versi .NET Framework).
  3. Isi nama project SCNilaiSiswa, tentukan folder, lalu klik Next.
  4. Pilih framework .NET 8.0 (Long Term Support), setelah itu klik Create.
  5. Hapus Form1.vb bawaan, lalu buat folder Forms untuk menampung FormUtama dan FormPengaturan.

Lewat terminal, perintah setaranya adalah dotnet new winforms -lang VB -n SCNilaiSiswa. Setelah itu, buka file .vbproj dan sesuaikan isinya seperti bagian berikut.

Isi File SCNilaiSiswa.vbproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <RootNamespace>SCNilaiSiswa</RootNamespace>
    <AssemblyName>SCNilaiSiswa</AssemblyName>
    <StartupObject>SCNilaiSiswa.Program</StartupObject>
    <MyType>WindowsFormsWithCustomSubMain</MyType>

    <OptionExplicit>On</OptionExplicit>
    <OptionStrict>On</OptionStrict>
    <OptionInfer>On</OptionInfer>
    <Product>SC Nilai Siswa VB.NET</Product>
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <Import Include="System.Drawing" />
    <Import Include="System.Windows.Forms" />
  </ItemGroup>

</Project>

Ada tiga bagian penting di file ini. Pertama, net8.0-windows bersama UseWindowsForms mengaktifkan Windows Forms. Kedua, MyType bernilai WindowsFormsWithCustomSubMain karena kita memakai Sub Main sendiri di Program.vb, bukan Application Framework bawaan VB. Ketiga, OptionStrict On memaksa setiap konversi tipe tertulis jelas, sehingga kesalahan seperti menjumlahkan String dengan Decimal langsung tertangkap saat build.

Struktur Folder dan File

sc-nilai-siswa-vbnet/
├── SCNilaiSiswa.sln
└── SCNilaiSiswa/
    ├── SCNilaiSiswa.vbproj
    ├── Program.vb  Model.vb  Penyimpanan.vb
    ├── Repo.vb  Rekap.vb  Util.vb
    └── Forms/
        ├── FormUtama.vb / .Designer.vb
        └── FormPengaturan.vb / .Designer.vb
FileFungsi
Program.vbTitik masuk, budaya id-ID, memuat data, membuka FormUtama
Model.vbClass Siswa dan Pengaturan
Penyimpanan.vbMuat dan simpan JSON di folder data
Repo.vbData di memori, rumus nilai akhir, predikat, tuntas, ranking, data contoh
Rekap.vbCetak rekap A4 multi halaman lewat PrintDocument
Util.vbWarna tema, gaya DataGridView, kotak pesan, pembungkus CSV
Forms\FormUtamaLayar utama: input, tabel, statistik, ekspor, cetak
Forms\FormPengaturanIdentitas kelas, bobot penilaian, KKM

Pembagian ini sengaja ketat: form tidak pernah menulis file, sedangkan Repo tidak pernah menyentuh kontrol. Akibatnya, rumus penilaian mudah Anda ganti tanpa membongkar tampilan.

Arsitektur Aplikasi Nilai Siswa VB.NET

Diagram arsitektur aplikasi nilai siswa VB.NET: Form, Repo, Penyimpanan JSON, dan file data
Empat lapisan SC Nilai Siswa, dari form sampai file JSON.

Diagram di atas memperlihatkan empat lapisan. Lapisan teratas berisi FormUtama, FormPengaturan, dan class Rekap untuk mencetak. Ketiganya hanya membaca DaftarSiswa serta Kelas, lalu memanggil fungsi milik module Repo.

Selanjutnya, Repo menampung data di memori sekaligus memegang semua rumus. Repo kemudian meminta module Penyimpanan membaca atau menulis JSON lewat System.Text.Json. Terakhir, file fisik berada di folder data\ di samping SCNilaiSiswa.exe.

Keputusan desain terpenting ada di sini: nilai akhir, predikat, dan ranking tidak ikut tersimpan. Ketiganya bergantung pada bobot dan KKM, jadi Repo selalu menghitungnya ulang. Dengan demikian, saat guru mengganti bobot, seluruh tabel langsung berubah tanpa perlu memperbarui data lama.

Langkah 1: Program.vb dan Budaya Angka Indonesia

Friend Module Program

    <STAThread>
    Friend Sub Main()
        ' Format angka & tanggal Indonesia (85,50 - 07 November 2026)
        Dim budaya As New CultureInfo("id-ID")
        CultureInfo.DefaultThreadCurrentCulture = budaya
        CultureInfo.DefaultThreadCurrentUICulture = budaya
        Thread.CurrentThread.CurrentCulture = budaya
        Thread.CurrentThread.CurrentUICulture = budaya

        Application.SetHighDpiMode(HighDpiMode.SystemAware)
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)

        AddHandler Application.ThreadException,
            Sub(s, e) Galat("Terjadi kesalahan: " & e.Exception.Message)

        Repo.MuatSemua()
        Application.Run(New FormUtama())
    End Sub

End Module

Empat baris pertama mengatur budaya id-ID untuk semua thread. Hasilnya, angka tampil sebagai 85,50 dengan koma desimal, sedangkan tanggal tampil sebagai 08 November 2026. Selain itu, Application.ThreadException menangkap error yang lolos lalu menampilkannya lewat kotak pesan Galat, sehingga aplikasi tidak langsung tertutup. Terakhir, Repo.MuatSemua() membaca data sebelum FormUtama terbuka.

Langkah 2: Model Data Siswa dan Pengaturan

Public Class Siswa
    Public Property Nis As String = ""
    Public Property Nama As String = ""
    Public Property NilaiTugas As Decimal
    Public Property NilaiUts As Decimal
    Public Property NilaiUas As Decimal
End Class

' Identitas kelas, bobot, dan KKM
Public Class Pengaturan
    Public Property NamaSekolah As String = "SMA Sharecode Mandiri"
    Public Property Kelas As String = "XI IPA 1"
    Public Property MataPelajaran As String = "Matematika"
    Public Property TahunAjaran As String = "2026/2027"
    Public Property Semester As String = "Ganjil"
    Public Property Guru As String = "Nur Sharecode, S.Pd."

    ' ... NipGuru dan Kota

    ' Bobot dalam persen, totalnya wajib 100
    Public Property BobotTugas As Integer = 30
    Public Property BobotUts As Integer = 30
    Public Property BobotUas As Integer = 40
    Public Property Kkm As Decimal = 75
End Class

Class Siswa hanya memuat lima property, yaitu NIS, nama, dan tiga nilai mentah. Tipe Decimal sengaja kita pilih ketimbang Double karena nilai rapor butuh pembulatan presisi, misalnya 82,35 tidak boleh berubah menjadi 82,34999.

Sementara itu, class Pengaturan menampung identitas kelas, bobot dalam persen, dan KKM. Nilai default-nya langsung terisi lewat inisialisasi property, sehingga pengaturan.json yang belum ada tetap menghasilkan objek lengkap. Gambar berikut memperlihatkan bentuk JSON dari kedua class tersebut.

Struktur file siswa.json dan pengaturan.json pada aplikasi nilai siswa VB.NET
Isi siswa.json dan pengaturan.json. Nilai akhir dan ranking sengaja tidak tersimpan.

Langkah 3: Penyimpanan JSON dengan System.Text.Json

Module Penyimpanan adalah satu-satunya bagian yang menyentuh disk. Isinya generik, jadi satu pasang method cukup untuk dua file yang berbeda bentuk.

Folder data dan Method Muat(Of T)

Private ReadOnly Opsi As New JsonSerializerOptions With {.WriteIndented = True}

Public ReadOnly Property FolderData As String
    Get
        Dim folder = Path.Combine(AppContext.BaseDirectory, "data")
        Directory.CreateDirectory(folder)
        Return folder
    End Get
End Property

' Baca file JSON menjadi objek bertipe T
Public Function Muat(Of T As {Class, New})(namaFile As String) As T
    Dim lokasi = Path.Combine(FolderData, namaFile)
    If Not File.Exists(lokasi) Then Return New T()
    Try
        Dim hasil = JsonSerializer.Deserialize(Of T)(File.ReadAllText(lokasi), Opsi)
        Return If(hasil, New T())

    Catch ex As JsonException
        ' File rusak: simpan salinannya agar data lama tidak hilang begitu saja
        File.Copy(lokasi, lokasi & ".rusak-" & DateTime.Now.ToString("yyyyMMddHHmmss"), True)
        Galat("File " & namaFile & " rusak dan tidak bisa dibaca." & vbCrLf &
              "Salinannya sudah disimpan di folder data, aplikasi memakai data kosong.")
        Return New T()
    End Try
End Function

Property FolderData menggabungkan AppContext.BaseDirectory dengan nama folder data, lalu membuat folder itu bila belum ada. Kemudian, Muat(Of T) mengembalikan objek baru jika file belum ada. Constraint {Class, New} memungkinkan pemanggilan New T() di dalam method generik.

Blok Catch menangani JSON yang rusak, misalnya akibat guru mengedit file lewat Notepad. Alih-alih membuang data, method ini menyalin file rusak ke nama berakhiran .rusak-waktu, memberi peringatan, lalu memakai data kosong.

Menyimpan Aman Lewat File .tmp

Public Sub Simpan(Of T)(namaFile As String, data As T)
    Dim lokasi = Path.Combine(FolderData, namaFile)
    Dim sementara = lokasi & ".tmp"
    ' Tulis ke file sementara dulu, baru ganti file asli (aman bila listrik mati)
    File.WriteAllText(sementara, JsonSerializer.Serialize(data, Opsi))
    File.Move(sementara, lokasi, True)
End Sub

Method Simpan(Of T) tidak langsung menimpa siswa.json. Sebaliknya, method ini menulis ke file .tmp terlebih dahulu, lalu File.Move dengan parameter True mengganti file asli. Jika listrik padam saat proses menulis, file lama tetap utuh karena hanya file sementara yang rusak.

Langkah 4: Repo, Rumus Nilai Akhir, Predikat, dan Ranking

Module Repo adalah jantung aplikasi. Selain menampung data di memori, module ini memegang seluruh rumus penilaian, sehingga Anda cukup mengubah satu file bila kurikulum sekolah berbeda.

Memuat Data dan Mengisi Data Contoh

Private Const FILE_SISWA As String = "siswa.json"
Private Const FILE_PENGATURAN As String = "pengaturan.json"

' Data kelas di memori
Public DaftarSiswa As New List(Of Siswa)()
Public Kelas As New Pengaturan()

Public Sub MuatSemua()
    Dim pertamaKali = Not AdaFile(FILE_SISWA)

    DaftarSiswa = Muat(Of List(Of Siswa))(FILE_SISWA)
    Kelas = Muat(Of Pengaturan)(FILE_PENGATURAN)

    If pertamaKali Then
        IsiDataContoh()
        SimpanSiswa()
        SimpanPengaturan()
    End If
End Sub

' Cari siswa berdasarkan NIS (huruf besar/kecil tidak berpengaruh)
Public Function CariSiswa(nis As String) As Siswa
    Return DaftarSiswa.FirstOrDefault(
        Function(s) String.Equals(s.Nis, nis.Trim(), StringComparison.OrdinalIgnoreCase))
End Function

Variabel pertamaKali mencatat apakah siswa.json sudah ada sebelum proses muat. Jika belum, IsiDataContoh menambahkan 12 siswa contoh lalu langsung menyimpannya, jadi aplikasi tidak tampil kosong saat pertama kali jalan. Fungsi CariSiswa memakai OrdinalIgnoreCase dan Trim(), sehingga NIS dengan spasi di ujung tetap cocok.

Rumus Nilai Akhir Berbobot

''' <summary>
''' Nilai akhir = (Tugas x bobot + UTS x bobot + UAS x bobot) / 100,
''' dibulatkan 2 desimal.
''' </summary>
Public Function NilaiAkhir(s As Siswa) As Decimal
    Dim total = s.NilaiTugas * Kelas.BobotTugas +
                s.NilaiUts * Kelas.BobotUts +
                s.NilaiUas * Kelas.BobotUas
    Return Math.Round(total / 100D, 2, MidpointRounding.AwayFromZero)
End Function

Rumus ini mengalikan setiap nilai dengan bobotnya, menjumlahkan hasilnya, lalu membaginya dengan 100. Misalnya Budi mendapat Tugas 80, UTS 85, dan UAS 82 dengan bobot 30/30/40. Hasilnya adalah (2400 + 2550 + 3280) / 100 = 82,30.

Perhatikan juga MidpointRounding.AwayFromZero. Secara default .NET memakai pembulatan bankir, sehingga 0,125 menjadi 0,12. Padahal guru terbiasa membulatkan setengah ke atas, jadi opsi ini membuat 0,125 menjadi 0,13 seperti hitungan manual.

Predikat dan Status Tuntas KKM

''' <summary>A: 90-100, B: 80-89,99, C: 70-79,99, D: di bawah 70.</summary>
Public Function Predikat(nilai As Decimal) As String
    If nilai >= 90 Then Return "A"
    If nilai >= 80 Then Return "B"
    If nilai >= 70 Then Return "C"
    Return "D"
End Function

' Tuntas bila nilai akhir mencapai KKM kelas
Public Function Tuntas(nilai As Decimal) As Boolean
    Return nilai >= Kelas.Kkm
End Function

Fungsi Predikat memeriksa ambang mulai dari yang tertinggi. Karena setiap If langsung Return, urutan pemeriksaan menentukan hasil: 92 berhenti di A, sedangkan 78,7 lolos sampai C. Sementara itu, Tuntas membandingkan nilai dengan KKM milik pengaturan kelas, jadi KKM 75 atau 70 cukup Anda ubah dari FormPengaturan.

Ranking Kompetisi 1-2-2-4

''' <summary>
''' Ranking kompetisi (1-2-2-4): nilai akhir sama mendapat peringkat sama,
''' peringkat berikutnya melompati jumlah siswa yang seri. Kunci = NIS.
''' </summary>
Public Function HitungRanking() As Dictionary(Of String, Integer)
    Dim hasil As New Dictionary(Of String, Integer)(StringComparer.OrdinalIgnoreCase)
    Dim nilai = DaftarSiswa.Select(Function(s) New With {s.Nis, .Akhir = NilaiAkhir(s)}).ToList()

    For Each n In nilai
        Dim akhir = n.Akhir
        hasil(n.Nis) = 1 + nilai.Where(Function(x) x.Akhir > akhir).Count()
    Next
    Return hasil
End Function

Ranking memakai pola kompetisi, sama seperti klasemen olahraga. Untuk setiap siswa, peringkatnya sama dengan 1 plus jumlah siswa yang nilainya lebih tinggi. Akibatnya, Budi dan Fajar yang sama-sama 82,30 berbagi peringkat 5, sedangkan Ahmad dengan 81,70 langsung mendapat peringkat 7.

Hasilnya berupa Dictionary berkunci NIS, sehingga tabel, CSV, dan rekap cetak cukup memanggil rank(s.Nis). Kompleksitasnya memang O(n²), namun untuk satu kelas berisi 30–50 siswa prosesnya tetap instan.

Alur hitung nilai akhir, predikat, KKM, dan ranking pada aplikasi nilai siswa VB.NET
Alur dari input nilai sampai ranking, lengkap dengan contoh hitung.

Langkah 5: Mendesain FormUtama

FormUtama berukuran 1100×660 piksel dengan tiga area, yaitu header gelap di atas, panel input di kiri, dan area isi di kanan. Semua kontrol berasal dari Form Designer, sehingga Anda bisa menggeser posisinya tanpa menyentuh kode.

Daftar Kontrol FormUtama

Nama kontrolTipeFungsi
pnlHeader, lblIdentitasPanel (Dock Top), LabelHeader dan identitas kelas
btnCetak, btnEkspor, btnPengaturanButtonCetak rekap, ekspor CSV, buka pengaturan
txtNis, txtNamaTextBoxNIS dan nama siswa
numTugas, numUts, numUasNumericUpDown (DecimalPlaces 1)Nilai 0–100
lblPratinjauLabelPratinjau nilai akhir dan predikat
btnSimpan, btnBaru, btnHapusButtonSimpan, kosongkan form, hapus siswa
lblJumlah, lblRata, lblTertinggi, lblTerendah, lblTuntasLabelIsi lima kartu statistik
txtCari, lblBobotTextBox, LabelPencarian dan ringkasan bobot
dgvSiswaDataGridViewSepuluh kolom, colNo sampai colRanking

Aktifkan juga property KeyPreview = True pada form agar pintasan Ctrl+S bekerja dari kontrol mana pun.

Event Load dan Pratinjau Nilai Akhir

Private Sub FormUtama_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    GayaGrid(dgvSiswa)
    RataKanan(colTugas, colUts, colUas, colAkhir)
    RataTengah(colNo, colPredikat, colKet, colRanking)
    colAkhir.DefaultCellStyle.Format = "N2"
    ' ... format 0.# untuk kolom Tugas, UTS, UAS

    For Each n In {numTugas, numUts, numUas}
        n.Maximum = 100
        AddHandler n.ValueChanged, AddressOf PerbaruiPratinjau
    Next

    TampilkanIdentitas()
    TampilkanData()
    SiswaBaru()
End Sub

' Hitung nilai akhir sementara setiap kali angka berubah
Private Sub PerbaruiPratinjau(sender As Object, e As EventArgs)
    Dim s As New Siswa With {.NilaiTugas = numTugas.Value, .NilaiUts = numUts.Value, .NilaiUas = numUas.Value}
    Dim akhir = NilaiAkhir(s)
    lblPratinjau.Text = $"Nilai akhir: {akhir:N2}  ({Predikat(akhir)})"
    lblPratinjau.ForeColor = If(Tuntas(akhir), Warna.Sukses, Warna.Bahaya)
End Sub

Saat form terbuka, GayaGrid dari Util.vb memberi gaya seragam pada tabel, lalu RataKanan dan RataTengah mengatur perataan kolom. Loop For Each kemudian memasang satu handler ValueChanged untuk ketiga NumericUpDown. Akibatnya, setiap kali guru mengetik angka, PerbaruiPratinjau menghitung nilai akhir dan mewarnai label hijau atau merah.

Menampilkan Tabel dan Warna Ketuntasan

Private Sub TampilkanData()
    Dim rank = HitungRanking()
    Dim kata = txtCari.Text.Trim()
    Dim data = DaftarSiswa.AsEnumerable()
    If kata <> "" Then
        data = data.Where(Function(s) s.Nis.Contains(kata, StringComparison.OrdinalIgnoreCase) OrElse
                                      s.Nama.Contains(kata, StringComparison.OrdinalIgnoreCase))
    End If

    ' Isi ulang tabel, urut nama
    dgvSiswa.Rows.Clear()
    Dim no = 0
    For Each s In data.OrderBy(Function(x) x.Nama)
        no += 1
        Dim akhir = NilaiAkhir(s)
        Dim lulus = Tuntas(akhir)
        Dim i = dgvSiswa.Rows.Add(no, s.Nis, s.Nama, s.NilaiTugas, s.NilaiUts, s.NilaiUas, akhir,
                                  Predikat(akhir), If(lulus, "Tuntas", "Belum Tuntas"), rank(s.Nis))

        ' Hijau untuk Tuntas, merah untuk Belum Tuntas
        Dim sel = dgvSiswa.Rows(i).Cells(colKet.Index)
        sel.Style.ForeColor = If(lulus, Warna.Sukses, Warna.Bahaya)
        sel.Style.SelectionForeColor = sel.Style.ForeColor
    Next
    dgvSiswa.ClearSelection()
    TampilkanStatistik()
End Sub

Pertama, method ini menghitung ranking untuk seluruh kelas, bukan hanya hasil pencarian. Dengan begitu, peringkat tetap benar walaupun tabel sedang tersaring. Selanjutnya, sel keterangan mendapat warna hijau atau merah. Baris SelectionForeColor menyamakan warna saat baris terpilih, sehingga teks merah tidak berubah hitam ketika guru mengklik baris.

Kartu Statistik Kelas

Private Sub TampilkanStatistik()
    Dim nilai = DaftarSiswa.Select(Function(s) NilaiAkhir(s)).ToList()
    lblJumlah.Text = nilai.Count.ToString()
    If nilai.Count = 0 Then
        lblRata.Text = "-" : lblTertinggi.Text = "-" : lblTerendah.Text = "-" : lblTuntas.Text = "-"
        Return
    End If

    ' Rata-rata, tertinggi, terendah, dan jumlah tuntas
    Dim jumlahTuntas = nilai.Where(Function(n) Tuntas(n)).Count()
    lblRata.Text = nilai.Average().ToString("N2")
    lblTertinggi.Text = nilai.Max().ToString("N2")
    lblTerendah.Text = nilai.Min().ToString("N2")
    lblTuntas.Text = $"{jumlahTuntas} ({jumlahTuntas * 100.0 / nilai.Count:0}%)"
End Sub

Kartu statistik memakai LINQ, yaitu Average, Max, dan Min atas daftar nilai akhir. Namun, ketiga fungsi itu melempar exception jika daftar kosong, karena itu ada pemeriksaan nilai.Count = 0 di awal. Perkalian dengan 100.0 mengubah hasil menjadi Double, lalu format :0 membulatkan persentase tanpa desimal.

Menyimpan Siswa, NIS Unik, dan Mode Ubah

Private Sub Simpan()
    Dim nis = txtNis.Text.Trim()
    Dim nama = txtNama.Text.Trim()
    If nis = "" Then Peringatan("NIS wajib diisi.") : txtNis.Focus() : Return
    If nama = "" Then Peringatan("Nama siswa wajib diisi.") : txtNama.Focus() : Return

    ' Siswa baru atau ubah nilai?
    Dim s = CariSiswa(nis)
    If s Is Nothing Then
        s = New Siswa With {.Nis = nis}
        DaftarSiswa.Add(s)
    ElseIf Not _modeUbah Then
        Peringatan($"NIS {nis} sudah dipakai oleh ""{s.Nama}"". Klik barisnya di tabel bila ingin mengubah nilai.")
        txtNis.Focus()
        Return
    End If

    s.Nama = nama
    s.NilaiTugas = numTugas.Value
    s.NilaiUts = numUts.Value
    s.NilaiUas = numUas.Value
    SimpanSiswa()

    TampilkanData()
    SiswaBaru()
    lblInfo.Text = $"✔ Tersimpan: {nis} - {nama} (nilai akhir {NilaiAkhir(s):N2})"
End Sub

Validasi awal memastikan NIS dan nama terisi. Setelah itu, CariSiswa menentukan alur. Jika NIS belum ada, method membuat Siswa baru. Namun, jika NIS sudah ada sementara form tidak dalam mode ubah, method menolak karena NIS harus unik. Terakhir, SimpanSiswa menulis JSON, tabel menyegarkan diri, dan form kembali kosong.

Klik Baris, Hapus, dan Pintasan Ctrl+S

Private Sub dgvSiswa_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvSiswa.CellClick
    If e.RowIndex < 0 Then Return
    Dim nis = Convert.ToString(dgvSiswa.Rows(e.RowIndex).Cells(colNis.Index).Value)
    Dim s = CariSiswa(nis)
    If s IsNot Nothing Then IsiForm(s)
End Sub

' Hapus hanya berlaku dalam mode ubah
Private Sub Hapus()
    If Not _modeUbah Then Return
    Dim s = CariSiswa(txtNis.Text)
    If s Is Nothing Then Return
    If Not Tanya($"Hapus data nilai ""{s.Nama}"" (NIS {s.Nis})?") Then Return

    DaftarSiswa.Remove(s)
    SimpanSiswa()
    TampilkanData()
    SiswaBaru()
End Sub

' Ctrl+S dari kontrol mana pun (KeyPreview = True)
Private Sub FormUtama_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    If e.Control AndAlso e.KeyCode = Keys.S Then
        e.SuppressKeyPress = True
        Simpan()
    End If
End Sub

Event CellClick mengambil NIS dari baris yang terklik lalu memanggil IsiForm. Method tersebut mengunci txtNis agar NIS tidak berubah, mengaktifkan tombol Hapus, dan menyalakan mode ubah. Sebelum menghapus, Tanya menampilkan konfirmasi Ya/Tidak. Sementara itu, SuppressKeyPress mencegah bunyi “ding” bawaan Windows.

Langkah 6: FormPengaturan dan Bobot Wajib 100%

Form pengaturan kelas, bobot penilaian, dan KKM pada aplikasi nilai siswa VB.NET
FormPengaturan: identitas kelas di atas, bobot dan KKM di bawah.

FormPengaturan terbuka sebagai dialog modal dari tombol Pengaturan. Bagian atas berisi identitas kelas untuk header dan kop rekap, sedangkan bagian bawah berisi bobot serta KKM.

Nama kontrolTipeFungsi
txtSekolah, txtKelas, txtMapel, txtTahunTextBoxIdentitas kelas
cboSemesterComboBoxGanjil atau Genap
txtGuru, txtNip, txtKotaTextBoxData tanda tangan rekap
numBTugas, numBUts, numBUas, numKkmNumericUpDownBobot persen dan KKM
lblTotalLabelTotal bobot berwarna
btnSimpan, btnBatalButtonAcceptButton dan CancelButton

Validasi Total Bobot

Private Function TotalBobot() As Integer
    Return CInt(numBTugas.Value + numBUts.Value + numBUas.Value)
End Function

' Satu handler untuk tiga NumericUpDown
Private Sub PerbaruiTotal(sender As Object, e As EventArgs) Handles numBTugas.ValueChanged, numBUts.ValueChanged, numBUas.ValueChanged
    Dim total = TotalBobot()
    If total = 100 Then
        lblTotal.Text = "Total bobot: 100% ✔"
        lblTotal.ForeColor = Warna.Sukses
    Else
        lblTotal.Text = $"Total bobot: {total}% (harus tepat 100%)"
        lblTotal.ForeColor = Warna.Bahaya
    End If
End Sub

' Tombol Simpan (dipangkas)
Private Sub btnSimpan_Click(sender As Object, e As EventArgs) Handles btnSimpan.Click
    ' ... validasi sekolah, kelas, mapel
    If TotalBobot() <> 100 Then
        Peringatan($"Total bobot Tugas + UTS + UAS harus 100%, sekarang {TotalBobot()}%.")
        Return
    End If
    ' ... salin semua isian ke Kelas
    Kelas.Kkm = numKkm.Value
    SimpanPengaturan()
    DialogResult = DialogResult.OK
    Close()
End Sub

Klausa Handles berisi tiga kontrol sekaligus, jadi satu method cukup menangani ketiganya. Label total berubah hijau saat jumlahnya tepat 100, sebaliknya merah bila belum. Setelah dialog tertutup dengan DialogResult.OK, FormUtama memanggil TampilkanData lagi, sehingga seluruh nilai akhir dan ranking langsung mengikuti bobot baru.

Langkah 7: Ekspor CSV yang Rapi di Excel

Banyak sekolah tetap mengolah leger di Excel. Oleh karena itu, tombol Ekspor CSV menghasilkan file yang langsung rapi saat Anda membukanya di Excel versi Indonesia.

Membungkus Nilai dengan Fungsi Csv

''' <summary>Membungkus nilai CSV (pemisah titik koma, cocok untuk Excel Indonesia).</summary>
Public Function Csv(nilai As Object) As String
    Dim teks = Convert.ToString(nilai, Indo)
    If teks.IndexOfAny({";"c, """"c, ControlChars.Cr, ControlChars.Lf}) >= 0 Then
        teks = """" & teks.Replace("""", """""") & """"
    End If
    Return teks
End Function

Excel berbahasa Indonesia memakai titik koma sebagai pemisah daftar, karena koma sudah menjadi pemisah desimal. Fungsi Csv mengubah nilai memakai budaya id-ID, lalu membungkusnya dengan tanda kutip bila teks mengandung titik koma, kutip, atau baris baru. Selain itu, kutip di dalam teks berubah menjadi dua kutip sesuai standar CSV.

Method EksporCsv

Using dlg As New SaveFileDialog()
    dlg.Filter = "File CSV (*.csv)|*.csv"
    dlg.FileName = $"nilai_{Kelas.Kelas}_{Kelas.MataPelajaran}.csv".Replace(" ", "_")
    If dlg.ShowDialog(Me) <> DialogResult.OK Then Return

    Dim rank = HitungRanking()
    Dim baris As New List(Of String) From {
        $"Rekap Nilai;{Csv(Kelas.NamaSekolah)}",
        ' ... baris identitas kelas, bobot, dan KKM
        "",
        "No;NIS;Nama;Tugas;UTS;UAS;Nilai Akhir;Predikat;Keterangan;Ranking"
    }

    ' Satu baris per siswa, urut nama
    Dim no = 0
    For Each s In DaftarSiswa.OrderBy(Function(x) x.Nama)
        no += 1
        Dim akhir = NilaiAkhir(s)
        baris.Add(String.Join(";", no, Csv(s.Nis), Csv(s.Nama), Csv(s.NilaiTugas), Csv(s.NilaiUts), Csv(s.NilaiUas),
                              Csv(akhir), Predikat(akhir), If(Tuntas(akhir), "Tuntas", "Belum Tuntas"), rank(s.Nis)))
    Next

    ' UTF-8 dengan BOM agar terbaca benar saat dibuka di Excel
    File.WriteAllLines(dlg.FileName, baris, New UTF8Encoding(True))
End Using

Beberapa baris pertama berisi kop, yaitu nama sekolah, identitas kelas, bobot, KKM, lalu judul kolom. Setelah itu, setiap siswa menghasilkan satu baris berurutan nama. Bagian terpenting ada di New UTF8Encoding(True), yaitu UTF-8 dengan BOM. Tanpa BOM, Excel sering salah membaca nama yang memuat huruf khusus.

Langkah 8: Cetak Rekap A4 dengan PrintDocument

Class Rekap menggambar laporan langsung dengan GDI+ di atas PrintDocument. Cara ini memang lebih panjang ketimbang report designer, namun hasilnya tidak butuh paket tambahan dan tetap jalan di semua edisi Visual Studio.

Membuat Dokumen dan Print Preview

Public Shared Function BuatDokumen() As PrintDocument
    Dim r As New Rekap()
    Dim doc As New PrintDocument()
    doc.DocumentName = $"Rekap Nilai {Kelas.Kelas} - {Kelas.MataPelajaran}"
    doc.DefaultPageSettings.PaperSize = New PaperSize("A4", 827, 1169)
    doc.DefaultPageSettings.Margins = New Margins(50, 50, 50, 40)
    AddHandler doc.BeginPrint, AddressOf r.Mulai
    AddHandler doc.PrintPage, AddressOf r.CetakHalaman
    AddHandler doc.Disposed, Sub(s, e) r.Bersihkan()
    Return doc
End Function

' Tampilkan jendela pratinjau
Public Shared Sub Tampilkan(pemilik As IWin32Window)
    Using doc = BuatDokumen(), dlg As New PrintPreviewDialog()
        dlg.Document = doc
        dlg.UseAntiAlias = True
        ' ... judul, ukuran, posisi jendela
        Try
            dlg.ShowDialog(pemilik)
        Catch ex As InvalidPrinterException
            Peringatan("Tidak ada printer yang terpasang. Pasang printer (atau Microsoft Print to PDF) untuk mencetak rekap.")
        End Try
    End Using
End Sub

Ukuran kertas memakai satuan seperseratus inci, jadi 827×1169 setara dengan A4. Handler BeginPrint mereset indeks siswa setiap kali proses cetak mulai, karena dokumen yang sama bisa melewati pratinjau lalu printer. Selain itu, blok Catch InvalidPrinterException memberi pesan ramah bila komputer belum punya printer sama sekali.

Halaman Bersambung Otomatis

Private Sub CetakHalaman(sender As Object, e As PrintPageEventArgs)
    Dim g = e.Graphics
    Dim area = e.MarginBounds
    Dim y As Single = area.Top
    Dim batasBawah As Single = area.Bottom - TINGGI_KAKI
    _halaman += 1
    ' ... kop di halaman 1, judul "(lanjutan)" di halaman berikutnya

    ' Tabel siswa
    If _indeks < _data.Count Then
        y = BarisTabel(g, area, y, Nothing)
        While _indeks < _data.Count AndAlso y + TINGGI_BARIS <= batasBawah
            y = BarisTabel(g, area, y, _data(_indeks))
            _indeks += 1
        End While
    End If

    ' Ringkasan + tanda tangan bila semua siswa sudah tercetak dan masih muat
    If _indeks >= _data.Count AndAlso y + 16 + TINGGI_PENUTUP <= batasBawah Then
        Penutup(g, area, y + 16)
        _penutupSelesai = True
    End If
    ' ... kaki halaman: waktu cetak dan nomor halaman

    e.HasMorePages = Not _penutupSelesai
End Sub

Variabel batasBawah menyisakan ruang untuk kaki halaman. Loop While menggambar baris siswa sampai ruang habis, lalu _indeks mengingat posisi terakhir. Jika masih ada sisa, HasMorePages = True membuat .NET memanggil event ini lagi untuk halaman berikutnya. Dalam uji coba dengan 50 siswa, rekap memanjang menjadi dua halaman dengan rapi.

Ringkasan dan Tanda Tangan Guru

Private Sub Penutup(g As Graphics, area As Rectangle, y As Single)
    Dim nilai = DaftarSiswa.Select(Function(s) NilaiAkhir(s)).ToList()
    Dim jumlahTuntas = nilai.Where(Function(n) Tuntas(n)).Count()
    ' ... gambar enam baris ringkasan di kiri

    ' Tanda tangan di kanan
    Dim xT = area.Right - 230
    Dim tempat = If(Kelas.Kota.Trim() = "", "", Kelas.Kota.Trim() & ", ")
    g.DrawString(tempat & DateTime.Now.ToString("dd MMMM yyyy"), _huruf, Brushes.Black, xT, y)
    g.DrawString("Guru Mata Pelajaran,", _huruf, Brushes.Black, xT, y + 18)
    Using garisBawah As New Font(_hurufTebal, FontStyle.Bold Or FontStyle.Underline)
        g.DrawString(Kelas.Guru, garisBawah, Brushes.Black, xT, y + 100)
    End Using
    If Kelas.NipGuru.Trim() <> "" Then
        g.DrawString("NIP. " & Kelas.NipGuru, _huruf, Brushes.Black, xT, y + 118)
    End If
End Sub

Bagian penutup mengulang statistik yang sama dengan kartu di layar. Kemudian, sisi kanan memuat kota, tanggal berformat Indonesia, nama guru bergaris bawah, dan NIP. Jika NIP kosong, barisnya tidak muncul sama sekali.

Hasil cetak rekap A4 aplikasi nilai siswa VB.NET dengan ringkasan dan tanda tangan guru
Rekap A4 hasil PrintDocument: kop kelas, tabel nilai, ringkasan, dan tanda tangan.

Contoh Kasus 1: Wali Kelas Menyusun Rapor dan Ranking Semester

Bu Nur Sharecode adalah guru Matematika sekaligus wali kelas XI IPA 1 di SMA Sharecode Mandiri. Menjelang pembagian rapor semester ganjil, beliau harus menyerahkan nilai akhir dan ranking kelas ke bagian kurikulum dalam dua hari.

  1. Buka Pengaturan, isi tahun ajaran 2026/2027 semester Ganjil, bobot 30/30/40, dan KKM 75.
  2. Masukkan nilai 13 siswa, termasuk siswa pindahan Zahra Kurnia. Pratinjau langsung memperlihatkan nilai akhir 81,70 (B) sebelum guru menekan tombol Simpan.
  3. Klik judul kolom Rank agar tabel urut dari peringkat 1.
  4. Tekan Cetak Rekap, lalu pilih Microsoft Print to PDF untuk lampiran leger.
Tabel ranking kelas pada aplikasi nilai siswa VB.NET setelah diurutkan berdasarkan peringkat
Urutan ranking kelas: dua pasang siswa berbagi peringkat 5 dan 7.

Hasilnya, Ayu Wulandari menempati peringkat 1 dengan 92,00. Berikutnya ada Siti Rahmawati 91,70 dan Indah Permatasari 89,80. Menariknya, Budi dan Fajar berbagi peringkat 5, begitu juga Ahmad dan Zahra di peringkat 7, sehingga peringkat 6 dan 8 tidak muncul. Kartu statistik juga langsung memberi bahan laporan, yaitu rata-rata kelas 80,85 dan 10 dari 13 siswa tuntas (77%). Untuk mata pelajaran lain, Bu Nur cukup menyalin folder aplikasi ke folder baru, karena setiap folder punya data sendiri.

Contoh Kasus 2: Guru Mapel Mencari Siswa yang Perlu Remedial

Setelah UAS, guru mata pelajaran wajib memberi remedial kepada siswa di bawah KKM. Daripada memeriksa baris satu per satu, guru cukup mengklik judul kolom Nilai Akhir sehingga nilai terendah naik ke atas.

Daftar siswa Belum Tuntas di bawah KKM pada aplikasi nilai siswa VB.NET untuk remedial
Urut nilai akhir terendah: tiga siswa Belum Tuntas langsung terlihat di atas.

Tiga baris merah langsung tampak, yaitu Muhammad Iqbal 66,30, Rizky Pratama 69,20, dan Dewi Anggraini 70,70. Guru lalu melihat komponen nilainya. Misalnya, Iqbal lemah di UTS (60), sedangkan Dewi paling rendah di UAS (68). Dengan demikian, materi remedial bisa menyasar komponen yang tepat.

Setelah remedial, guru mengklik baris Dewi dan mengganti UAS menjadi 80. Pratinjau segera menghitung (75×30 + 70×30 + 80×40) / 100 = 75,50, predikat C, dan warnanya berubah hijau karena sudah melewati KKM. Setelah guru menekan Ctrl+S, kartu Tuntas ikut naik. Terakhir, tombol Ekspor CSV menghasilkan file yang siap guru kirim ke wali kelas.

Menjalankan dan Membagikan Aplikasi

dotnet run --project SCNilaiSiswa

dotnet publish SCNilaiSiswa -c Release -r win-x64 --self-contained false -o publish

Perintah pertama menjalankan aplikasi tanpa Visual Studio. Sementara itu, perintah kedua menghasilkan folder publish berisi SCNilaiSiswa.exe yang siap Anda salin ke laptop guru. Komputer tujuan perlu .NET 8 Desktop Runtime. Jika tidak ingin memasang runtime, ganti opsi menjadi --self-contained true dengan konsekuensi ukuran file lebih besar.

Ide Pengembangan Lanjutan

  • Pindahkan data ke SQLite agar satu file database menampung banyak kelas dan mata pelajaran.
  • Komponen nilai yang fleksibel, misalnya beberapa nilai tugas harian dalam List(Of Decimal).
  • Impor daftar siswa dari file CSV hasil ekspor aplikasi dapodik atau Excel.
  • Ambang predikat yang bisa guru atur sendiri dari FormPengaturan.
  • Grafik distribusi nilai per predikat agar sebaran kemampuan kelas lebih mudah terbaca.
  • Backup otomatis folder data ke flashdisk atau cloud setiap kali aplikasi tertutup.

Kesalahan Umum

  • Membuka folder, bukan file .sln. Fitur Open Folder membuat project tidak bisa di-debug, jadi selalu buka SCNilaiSiswa.sln.
  • Memakai Visual Studio 2019. Versi ini tidak mengenal .NET 8, sehingga project gagal termuat. Perbarui ke VS 2022 17.8 atau lebih baru.
  • Lupa Option Strict. Tanpa OptionStrict On, konversi diam-diam seperti String ke Decimal baru ketahuan saat aplikasi jalan.
  • Budaya angka tidak cocok. CSV dengan pemisah titik koma akan bertumpuk dalam satu kolom bila Excel memakai format regional English.
  • Total bobot bukan 100. FormPengaturan memang menolak, tetapi pastikan juga rumus tambahan buatan Anda tetap membagi dengan 100.
  • File data terkunci. Menjalankan aplikasi dari dalam zip atau dari C:\Program Files membuat folder data gagal tertulis, jadi ekstrak dulu ke folder biasa.
  • Mengira peringkat hilang. Lompatan dari 5 ke 7 bukan bug, melainkan hasil ranking kompetisi untuk nilai yang seri.

Kesimpulan

Lewat tutorial ini, Anda sudah membangun aplikasi rekap nilai lengkap dengan Windows Forms .NET 8. Model yang ringkas, penyimpanan JSON yang aman, serta module Repo yang memegang semua rumus membuat bobot, predikat, KKM, dan ranking mudah Anda sesuaikan dengan kebijakan sekolah. Selain itu, ekspor CSV dan cetak rekap A4 membuat hasilnya langsung siap pakai untuk leger maupun rapor.

Jika ingin langsung mencoba atau mengembangkannya, download source code aplikasi nilai siswa VB.NET di sharecode.id secara gratis. Buka file .sln-nya, tekan F5, lalu mulai ubah rumus sesuai kurikulum sekolah Anda.

Baca Juga

Referensi: Desktop Guide (Windows Forms .NET), How to serialize and deserialize JSON in .NET (System.Text.Json), PrintDocument Class, DataGridView Control (Windows Forms).

Ali Akbar

Software Developer yang fokus mengembangkan aplikasi berbasis Web dan Desktop. Senang mempelajari teknologi baru terutama di bidang web design dan web development.

View all posts by Ali Akbar →

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *