Implementasi Proyek

Membangun aplikasi desktop yang lengkap dari perencanaan sampai aplikasi jadi.

Daftar isi

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.

xml PengelolaCatatan.vbproj
<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.

vb Catatan.vb
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 Class

Kelima 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.

vb PenyimpananCatatan.vb
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 Class

Arah 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.

vb UjiLogika.vb
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
text Keluaran
== 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.

xml Proyek console minimal untuk driver UjiLogika
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <OptionStrict>On</OptionStrict>
  </PropertyGroup>
</Project>
bash
# tidak-dijalankan
# masuk ke folder yang berisi Catatan.vb, PenyimpananCatatan.vb, UjiLogika.vb, dan berkas .vbproj console
cd UjiLogika
# mengompilasi dan menjalankan driver
dotnet run

Antarmuka 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.

vb FormUtama.vb
' 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 Module

Kode 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.

bash
# tidak-dijalankan
# masuk ke folder proyek
cd PengelolaCatatan
# membangun aplikasi
dotnet build
# menjalankan aplikasi di Windows
dotnet run

Di 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.

text Transkrip terminal
$ 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.06

Pada Windows, urutan uji coba kelima fitur dapat diikuti dalam beberapa langkah.

  1. Buka berkas PengelolaCatatan.vbproj melalui Visual Studio, atau jalankan dotnet run dari folder proyek.
  2. Tekan F5 untuk menjalankan aplikasi dengan debugger, atau Ctrl+F5 tanpa debugger.
  3. Tambahkan satu catatan melalui tombol Tambah dan periksa baris [1] pada panel daftar.
  4. Pilih baris tersebut, ubah isinya, lalu tekan Ubah untuk memastikan judul pada daftar ikut berubah.
  5. 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 menomori Id dari 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 penanda sedangMemuat agar penyegaran daftar tidak memicu event pengisian kotak isian.

Referensi#