Topik ini mewujudkan rancangan Pengelola Catatan Harian menjadi aplikasi yang utuh. Pembahasannya berjalan per berkas, mulai dari konfigurasi proyek, kelas data, lapisan penyimpanan JSON, driver console untuk menguji logika, sampai form utama beserta titik masuk program. Setiap berkas aplikasi divalidasi kompilasinya, lapisan logika dijalankan sungguhan pada console dengan keluaran asli, dan aplikasi dijalankan di Windows pada langkah terakhir.
Berkas proyek dan pengaturannya#
Proyek ini menggunakan satu berkas .vbproj bernama PengelolaCatatan.vbproj yang mengikat seluruh berkas kode menjadi satu aplikasi berjendela. Isinya ditampilkan berikut ini.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<OptionStrict>On</OptionStrict>
<OptionExplicit>On</OptionExplicit>
<OptionInfer>On</OptionInfer>
</PropertyGroup>
</Project>Properti OutputType bernilai WinExe sehingga hasil build berupa program berjendela, TargetFramework bernilai net10.0-windows memilih kerangka .NET untuk Windows, sedangkan UseWindowsForms bernilai true mengaktifkan pustaka Windows Forms. Tiga opsi OptionStrict, OptionExplicit, dan OptionInfer yang bernilai On mempertahankan pengaturan ketat yang menyertai seluruh contoh pada jalur ini. Properti EnableWindowsTargeting memungkinkan proyek dibangun dari sistem operasi mana pun, misalnya ketika kompilasi diperiksa secara otomatis di lingkungan tanpa layar Windows, dan tidak mengubah perilaku build di Windows.
Daftar berkas kode tidak dinyatakan satu per satu pada berkas proyek. SDK modern menyertakan setiap berkas .vb di dalam folder proyek secara otomatis, sehingga menambah kelas baru cukup dilakukan dengan menaruh berkasnya di folder yang sama dan melakukan build ulang. Perilaku ini membuat struktur berkas pada rancangan cukup diterapkan apa adanya tanpa pendaftaran tambahan.
Kelas data Catatan#
Berkas pertama yang ditulis adalah Catatan.vb karena kelas lain bergantung padanya. Isinya persis seperti rancangan pada topik perencanaan, yaitu lima properti tanpa satu baris perilaku pun. Menulisnya lebih dahulu membuat dua berkas sesudahnya dapat dikompilasi tanpa menunggu antarmuka selesai.
Public Class Catatan
Public Property Id As Integer
Public Property Judul As String = ""
Public Property Isi As String = ""
Public Property WaktuBuat As DateTime
Public Property WaktuUbah As DateTime
End ClassKelima properti ditulis sebagai properti otomatis, yaitu properti yang field penyimpanannya dibuat oleh compiler sehingga blok Get dan Set tidak pernah ditulis. Judul dan Isi diberi nilai awal berupa string kosong agar objek Catatan yang baru dibuat tidak pernah membawa Nothing pada kedua properti tersebut. Penugasan WaktuBuat dan WaktuUbah dibiarkan kepada lapisan logika, bukan kepada kelas data, sehingga kelas ini tetap bebas perilaku sesuai rancangannya. Ketika diserialisasi, nama properti inilah yang menjadi kunci JSON, sehingga isi catatan.json memuat kunci Id, Judul, Isi, WaktuBuat, dan WaktuUbah.
Lapisan penyimpanan PenyimpananCatatan#
Berkas PenyimpananCatatan.vb memegang seluruh aturan data, yaitu mengelola daftar catatan, membaca dan menulis catatan.json, menomori Id, serta menolak judul kosong.
Imports System.IO
Imports System.Text.Json
Public Class PenyimpananCatatan
Private Const NamaBerkas As String = "catatan.json"
Private ReadOnly opsiJson As New JsonSerializerOptions With {
.WriteIndented = True
}
Private daftar As New List(Of Catatan)()
Public ReadOnly Property Jumlah As Integer
Get
Return daftar.Count
End Get
End Property
Public Function Semua() As List(Of Catatan)
Return daftar
End Function
Public Sub Muat()
If File.Exists(NamaBerkas) Then
Dim json As String = File.ReadAllText(NamaBerkas)
Dim hasil As List(Of Catatan) = JsonSerializer.Deserialize(Of List(Of Catatan))(json, opsiJson)
If hasil IsNot Nothing Then
daftar = hasil
End If
End If
End Sub
Public Sub Simpan()
Dim json As String = JsonSerializer.Serialize(daftar, opsiJson)
File.WriteAllText(NamaBerkas, json)
End Sub
Public Function Tambah(judul As String, isi As String) As Boolean
If String.IsNullOrWhiteSpace(judul) Then
Return False
End If
Dim baru As New Catatan() With {
.Id = IdBerikutnya(),
.Judul = judul.Trim(),
.Isi = isi,
.WaktuBuat = DateTime.Now,
.WaktuUbah = DateTime.Now
}
daftar.Add(baru)
Simpan()
Return True
End Function
Public Function Ubah(id As Integer, judulBaru As String, isiBaru As String) As Boolean
If String.IsNullOrWhiteSpace(judulBaru) Then
Return False
End If
Dim target As Catatan = Cari(id)
If target Is Nothing Then
Return False
End If
target.Judul = judulBaru.Trim()
target.Isi = isiBaru
target.WaktuUbah = DateTime.Now
Simpan()
Return True
End Function
Public Function Hapus(id As Integer) As Boolean
Dim target As Catatan = Cari(id)
If target Is Nothing Then
Return False
End If
daftar.Remove(target)
Simpan()
Return True
End Function
Private Function Cari(id As Integer) As Catatan
Return daftar.Find(Function(c) c.Id = id)
End Function
Private Function IdBerikutnya() As Integer
Dim terbesar As Integer = 0
For Each c As Catatan In daftar
If c.Id > terbesar Then
terbesar = c.Id
End If
Next
Return terbesar + 1
End Function
End ClassArah data keluar dan masuk dipusatkan pada dua metode. Metode Muat membaca catatan.json melalui File.ReadAllText lalu mengubahnya menjadi daftar dengan JsonSerializer.Deserialize, dan daftar tetap kosong apabila berkasnya belum ada sehingga penggunaan pertama tidak menimbulkan error. Metode Simpan membalikkan arahnya melalui JsonSerializer.Serialize lalu menulis hasilnya dengan File.WriteAllText, dan opsi WriteIndented membuat JSON tercetak berbaris-baris agar berkasnya nyaman dibaca.
Ketiga operasi data mengikuti pola yang sama, yaitu memeriksa aturan, mengubah daftar, lalu memanggil Simpan di akhir. Karena itulah catatan.json selalu memuat keadaan terbaru setelah setiap perubahan, sesuai fitur simpan otomatis. Validasi judul dijalankan dengan String.IsNullOrWhiteSpace, sedangkan IdBerikutnya mengambil nilai Id terbesar lalu menambah satu, persis seperti aturan penomoran pada perencanaan.
Menguji logika tanpa antarmuka#
Lapisan logika dapat diuji tanpa membuka satu jendela pun dengan bantuan program console kecil yang disebut driver, yaitu program pendamping yang memanggil kelas yang diuji dan mencetak hasilnya. Driver diberi nama UjiLogika.vb dan berisi Module Program dengan Sub Main, sehingga dapat dikompilasi bersama Catatan.vb dan PenyimpananCatatan.vb sebagai proyek console biasa. Keluarannya dibaca langsung dari terminal, sehingga perilaku metode dapat dinilai tanpa menekan satu tombol pun di dalam form.
Imports System.IO
Module Program
Sub Main()
' mulai dari keadaan bersih agar percobaan dapat diulang
If File.Exists("catatan.json") Then
File.Delete("catatan.json")
End If
Dim penyimpanan As New PenyimpananCatatan()
penyimpanan.Muat()
Console.WriteLine("== Menambah tiga catatan ==")
penyimpanan.Tambah("Belanja mingguan", "Beli telur, beras, dan minyak goreng.")
penyimpanan.Tambah("Jadwal piket", "Piket kelas dipindah ke hari Kamis.")
penyimpanan.Tambah("Ide latihan", "Buat penghitung kata sederhana.")
Tampilkan(penyimpanan)
Console.WriteLine("== Mengubah catatan bernomor 2 ==")
penyimpanan.Ubah(2, "Jadwal piket kelas", "Piket kelas dipindah ke hari Jumat.")
Tampilkan(penyimpanan)
Console.WriteLine("== Menghapus catatan bernomor 1 ==")
penyimpanan.Hapus(1)
Tampilkan(penyimpanan)
Console.WriteLine("== Menambah catatan tanpa judul ==")
Dim berhasil As Boolean = penyimpanan.Tambah(" ", "Isian ini harusnya ditolak.")
Console.WriteLine($"Tambah dengan judul kosong: {berhasil}")
Console.WriteLine()
Console.WriteLine("== Isi berkas catatan.json ==")
Console.WriteLine(File.ReadAllText("catatan.json"))
End Sub
Private Sub Tampilkan(penyimpanan As PenyimpananCatatan)
For Each c As Catatan In penyimpanan.Semua()
Console.WriteLine($"[{c.Id}] {c.Judul}")
Console.WriteLine($" {c.Isi}")
Next
Console.WriteLine()
End Sub
End Module== Menambah tiga catatan ==
[1] Belanja mingguan
Beli telur, beras, dan minyak goreng.
[2] Jadwal piket
Piket kelas dipindah ke hari Kamis.
[3] Ide latihan
Buat penghitung kata sederhana.
== Mengubah catatan bernomor 2 ==
[1] Belanja mingguan
Beli telur, beras, dan minyak goreng.
[2] Jadwal piket kelas
Piket kelas dipindah ke hari Jumat.
[3] Ide latihan
Buat penghitung kata sederhana.
== Menghapus catatan bernomor 1 ==
[2] Jadwal piket kelas
Piket kelas dipindah ke hari Jumat.
[3] Ide latihan
Buat penghitung kata sederhana.
== Menambah catatan tanpa judul ==
Tambah dengan judul kosong: False
== Isi berkas catatan.json ==
[
{
"Id": 2,
"Judul": "Jadwal piket kelas",
"Isi": "Piket kelas dipindah ke hari Jumat.",
"WaktuBuat": "2026-11-03T14:48:47.2140854+00:00",
"WaktuUbah": "2026-11-03T14:48:47.2188357+00:00"
},
{
"Id": 3,
"Judul": "Ide latihan",
"Isi": "Buat penghitung kata sederhana.",
"WaktuBuat": "2026-11-03T14:48:47.2152825+00:00",
"WaktuUbah": "2026-11-03T14:48:47.2152836+00:00"
}
]Alur driver mengikuti daftar fitur pada rancangan, yaitu menambah tiga catatan, menelusurinya, mengubah catatan bernomor 2, menghapus catatan bernomor 1, menolak judul kosong, dan mencetak isi catatan.json sebagai bukti data benar-benar tersimpan. Nilai WaktuBuat dan WaktuUbah pada keluaran berbeda untuk setiap eksekusi karena diisi dengan DateTime.Now saat program berjalan. Penghapusan baris pertama tidak memicu penomoran ulang, sehingga catatan tersisa tetap menggunakan nomor 2 dan 3.
Driver memulai kerjanya dengan menghapus catatan.json yang tersisa dari percobaan sebelumnya, sehingga urutan uji yang sama selalu menghasilkan keluaran yang sebanding. Pendekatan ini merupakan bentuk sederhana dari pengujian unit, yaitu menguji potongan kode secara terpisah dari antarmukanya. Kebiasaan yang sama kemudian dilanjutkan dengan kerangka pengujian formal ketika aplikasi bertambah besar.
Driver memerlukan proyek console tersendiri yang terpisah dari proyek utama karena OutputType keduanya berbeda. Berkas proyek minimalnya cukup seperti berikut.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<OptionStrict>On</OptionStrict>
</PropertyGroup>
</Project># tidak-dijalankan
# masuk ke folder yang berisi Catatan.vb, PenyimpananCatatan.vb, UjiLogika.vb, dan berkas .vbproj console
cd UjiLogika
# mengompilasi dan menjalankan driver
dotnet runAntarmuka FormUtama#
Berkas terakhir adalah FormUtama.vb yang memuat dua bagian, yaitu kelas form dan titik masuk program. Aplikasi ber-OutputType WinExe menuntut satu Sub Main sebagai titik masuk, dan titik masuk itu sengaja ditempatkan bersama kelas formnya agar jumlah berkas proyek tetap seperti rancangan. Sub Main memanggil Application.EnableVisualStyles lalu Application.Run(New FormUtama()) untuk membuka jendela.
' tidak-dijalankan
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Public Class FormUtama
Inherits Form
Private WithEvents tombolTambah As New Button()
Private WithEvents tombolUbah As New Button()
Private WithEvents tombolHapus As New Button()
Private WithEvents daftar As New ListBox()
Private inputJudul As New TextBox()
Private inputIsi As New TextBox()
Private labelStatus As New Label()
Private penyimpanan As New PenyimpananCatatan()
Private sedangMemuat As Boolean
Sub New()
Text = "Pengelola Catatan Harian"
ClientSize = New Size(430, 380)
Dim labelJudul As New Label With {.Text = "Judul", .Location = New Point(15, 14), .AutoSize = True}
inputJudul.Location = New Point(70, 10)
inputJudul.Size = New Size(345, 23)
Dim labelIsi As New Label With {.Text = "Isi", .Location = New Point(15, 46), .AutoSize = True}
inputIsi.Location = New Point(70, 42)
inputIsi.Size = New Size(345, 80)
inputIsi.Multiline = True
tombolTambah.Text = "Tambah"
tombolTambah.Location = New Point(70, 134)
tombolTambah.Size = New Size(105, 28)
tombolUbah.Text = "Ubah"
tombolUbah.Location = New Point(185, 134)
tombolUbah.Size = New Size(105, 28)
tombolHapus.Text = "Hapus"
tombolHapus.Location = New Point(300, 134)
tombolHapus.Size = New Size(115, 28)
daftar.Location = New Point(15, 176)
daftar.Size = New Size(400, 160)
labelStatus.Text = "Jumlah catatan: 0"
labelStatus.Location = New Point(15, 348)
labelStatus.AutoSize = True
Controls.Add(labelJudul)
Controls.Add(inputJudul)
Controls.Add(labelIsi)
Controls.Add(inputIsi)
Controls.Add(tombolTambah)
Controls.Add(tombolUbah)
Controls.Add(tombolHapus)
Controls.Add(daftar)
Controls.Add(labelStatus)
End Sub
Private Sub FormUtama_Load(sender As Object, e As EventArgs) Handles MyBase.Load
penyimpanan.Muat()
MuatUlangDaftar()
End Sub
Private Sub TombolTambahDiklik(sender As Object, e As EventArgs) Handles tombolTambah.Click
If Not ValidasiJudul() Then
Return
End If
penyimpanan.Tambah(inputJudul.Text, inputIsi.Text)
BersihkanMasukan()
MuatUlangDaftar()
End Sub
Private Sub TombolUbahDiklik(sender As Object, e As EventArgs) Handles tombolUbah.Click
Dim id As Integer = AmbilIdTerpilih()
If id = -1 OrElse Not ValidasiJudul() Then
Return
End If
penyimpanan.Ubah(id, inputJudul.Text, inputIsi.Text)
MuatUlangDaftar()
End Sub
Private Sub TombolHapusDiklik(sender As Object, e As EventArgs) Handles tombolHapus.Click
Dim id As Integer = AmbilIdTerpilih()
If id = -1 Then
Return
End If
penyimpanan.Hapus(id)
BersihkanMasukan()
MuatUlangDaftar()
End Sub
Private Sub DaftarPilihanBerubah(sender As Object, e As EventArgs) Handles daftar.SelectedIndexChanged
If sedangMemuat Then
Return
End If
Dim id As Integer = AmbilIdTerpilih()
If id = -1 Then
Return
End If
Dim terpilih As Catatan = penyimpanan.Semua().Find(Function(c) c.Id = id)
If terpilih IsNot Nothing Then
inputJudul.Text = terpilih.Judul
inputIsi.Text = terpilih.Isi
End If
End Sub
Private Function ValidasiJudul() As Boolean
If String.IsNullOrWhiteSpace(inputJudul.Text) Then
MessageBox.Show("Judul tidak boleh kosong.", "Validasi", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return False
End If
Return True
End Function
Private Function AmbilIdTerpilih() As Integer
If daftar.SelectedItem Is Nothing Then
Return -1
End If
Dim teks As String = CStr(daftar.SelectedItem)
Dim posisi As Integer = teks.IndexOf("]"c)
Dim hasil As Integer = 0
If posisi > 0 AndAlso Integer.TryParse(teks.Substring(1, posisi - 1), hasil) Then
Return hasil
End If
Return -1
End Function
Private Sub MuatUlangDaftar()
sedangMemuat = True
daftar.Items.Clear()
For Each c As Catatan In penyimpanan.Semua()
daftar.Items.Add($"[{c.Id}] {c.Judul}")
Next
labelStatus.Text = $"Jumlah catatan: {penyimpanan.Jumlah}"
sedangMemuat = False
End Sub
Private Sub BersihkanMasukan()
inputJudul.Text = ""
inputIsi.Text = ""
End Sub
End Class
Module Program
<STAThread>
Sub Main()
Application.EnableVisualStyles()
Application.Run(New FormUtama())
End Sub
End ModuleKode di atas berjalan penuh di Windows dan tidak dieksekusi di lingkungan tanpa layar, tetapi kompilasinya tetap divalidasi pada bagian akhir topik ini. Konstruktor membangun seluruh kontrol secara manual, meliputi dua Label penunjuk, TextBox judul, TextBox isi bertanda Multiline, tiga tombol, ListBox, dan labelStatus, lalu mendaftarkannya dengan Controls.Add. Penangan Load memanggil Muat kemudian MuatUlangDaftar, sehingga catatan yang tersisa dari sesi sebelumnya tampil sejak form terbuka.
Setiap tombol mengikuti pola yang sama, yaitu memeriksa prasyarat, memanggil satu metode lapisan logika, lalu menyegarkan panel melalui MuatUlangDaftar. Pemilihan baris memicu event SelectedIndexChanged yang mengisi kembali kotak isian dari objek Catatan yang Id-nya diurai oleh AmbilIdTerpilih dari teks baris. Validasi judul menggunakan String.IsNullOrWhiteSpace dan menampilkan MessageBox apabila kosong, sesuai aturan pada rancangan.
Membangun dan menjalankan aplikasi#
Aplikasi dibangun dan dijalankan dari folder proyeknya dengan dua perintah berikut.
# tidak-dijalankan
# masuk ke folder proyek
cd PengelolaCatatan
# membangun aplikasi
dotnet build
# menjalankan aplikasi di Windows
dotnet runDi lingkungan verifikasi tanpa layar, seluruh berkas aplikasi diperiksa kompilasinya sebagai satu proyek dan berakhir tanpa error, sebagaimana transkrip berikut. Kompilasi semacam ini menjamin kelima berkas saling melengkapi, mulai dari nama kelas hingga tanda tangan metode yang dipanggil lintas berkas. Perintah pemeriksaannya menggunakan berkas PengelolaCatatan.vbproj yang sama dengan yang ditampilkan pada bagian pertama.
$ bash /home/kal/my-project/scripts/vb-build.sh /home/kal/my-project/build-scratch/pengerjaan-proyek__build-ui
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:01.06Pada Windows, urutan uji coba kelima fitur dapat diikuti dalam beberapa langkah.
- Buka berkas PengelolaCatatan.vbproj melalui Visual Studio, atau jalankan
dotnet rundari folder proyek. - Tekan F5 untuk menjalankan aplikasi dengan debugger, atau Ctrl+F5 tanpa debugger.
- Tambahkan satu catatan melalui tombol Tambah dan periksa baris
[1]pada panel daftar. - Pilih baris tersebut, ubah isinya, lalu tekan Ubah untuk memastikan judul pada daftar ikut berubah.
- Tutup aplikasi, buka kembali, dan pastikan catatan tampil dari catatan.json.
Kesalahan umum#
Kesalahan pertama adalah menaruh FormUtama.vb ke dalam proyek console yang digunakan oleh driver. Tipe Form tidak dikenal karena pustaka Windows Forms tidak diaktifkan pada proyek console, sehingga compiler melaporkan error bahwa tipe Form tidak didefinisikan. Perbaikannya adalah membangun aplikasi dengan PengelolaCatatan.vbproj yang memuat UseWindowsForms bernilai true dan menyimpan driver pada proyek console tersendiri.
Kesalahan kedua adalah membaca daftar.SelectedItem tanpa pemeriksaan terlebih dahulu. Properti itu bernilai Nothing selama belum ada baris terpilih, sehingga pemanggilan metode padanya melempar NullReferenceException ketika tombol ditekan. Fungsi AmbilIdTerpilih pada kode di atas menghindarinya dengan mengembalikan -1 apabila tidak ada pilihan, dan pemanggilnya berhenti lebih dahulu.
Kesalahan ketiga adalah mengira catatan.json hilang setelah aplikasi dijalankan. Berkas itu dibuat pada folder kerja aplikasi, yaitu folder bin di dalam proyek ketika aplikasi dijalankan dari Visual Studio, sedangkan dotnet run menjalankannya dari folder tempat perintah diketik. Periksa folder tersebut sebelum menyimpulkan bahwa penyimpanannya gagal.
Ringkasan#
- Aplikasi terdiri atas empat berkas inti, yaitu Catatan.vb, PenyimpananCatatan.vb, FormUtama.vb, dan PengelolaCatatan.vbproj, ditambah driver UjiLogika.vb untuk pengujian logika pada console.
- Lapisan logika menyimpan daftar catatan, membaca dan menulis catatan.json melalui
JsonSerializer, menolak judul kosong, dan menomoriIddari nilai terbesar. - Driver console menambah, menelusuri, mengubah, dan menghapus catatan, lalu mencetak isi catatan.json, sehingga aturan data terverifikasi tanpa antarmuka.
- Form menyambungkan setiap tombol ke satu metode logika, menolak judul kosong melalui
MessageBox, dan menggunakan penandasedangMemuatagar penyegaran daftar tidak memicu event pengisian kotak isian.
Referensi#
- dotnet run command — Microsoft Learn — Referensi perintah untuk membangun dan menjalankan proyek .NET dari folder proyeknya.
- JsonSerializer Class — Microsoft Learn — Referensi kelas yang membaca dan menulis JSON pada lapisan penyimpanan.
- File Class — Microsoft Learn
— Referensi metode
ReadAllTextdanWriteAllTextyang dipakai menyimpan catatan. - Form Class — Microsoft Learn — Referensi kelas dasar yang diwarisi FormUtama.
- MessageBox Class — Microsoft Learn — Referensi dialog yang menampilkan peringatan judul kosong.