Menerapkan MVVM Di Windows Presentation Foundation (WPF)


Seorang teman sedang giat mempersiapkan materi pelatihan untuk pattern Model View Controller (MVC) bagi mahasiswa pemula. Ia beberapa kali berkonsultasi dengan saya mengenai implementasi yang tepat. Sebagai informasi, Model View Controller (MVC) adalah pola yang diterapkan pada perancangan user interface/UI (presentation layer). Setelah upaya saya menunjukkan beberapa pola UI yang efektif, pada akhirnya teman tersebut lebih tertarik menerapkan pattern Model View View Model (MVVM). Boleh dibilang MVVM adalah sebuah variasi dari MVC yang memisahkan view menjadi dua, yaitu view dan view model yang terhubung melalui data binding. Saya sudah biasa memakai pola sejenis MVVM di framework Griffon. Pada kesempatan ini, saya akan membahas MVVM dengan contoh berupa sebuah aplikasi yang memakai Windows Presentation Foundation (WPF). Yup, menurut sejarah, MVVM pertama kali terlahir untuk keperluan WPF dan Silverlight (.NET Framework 3). Beberapa programmer (termasuk saya) kerap memodifikasi pola ini dengan menambahkan sebuah controller agar tidak mencemari view model. Walaupun demikian, saya akan tetap menyebutnya sebagai pola MVVM (karena ketergantungan pada penggunaan binding dan view model). Atau, apa perlu membuat istilah baru: MVVMC ?😉

Saya akan mulai dengan membuat sebuah proyek baru di Visual Studio 2010. Saya memilih jenis proyek Visual C#, Windows, WPF Application untuk membuat aplikasi desktop yang memakai WPF.

Yang dimaksud dengan model dalam MVVM adalah domain model atau entity yang nantinya disimpan ke database. Seluruh nilai dan operasi yang berkaitan dengan permasalahan yang dihadapi (business logic) harus diletakkan disini! Saya akan membuat sebuah model baru dengan men-klik kanan nama proyek dan memilih Add, Class…. Saya memberi nama model ini sebagai ItemPenjualan yang kode programnya terlihat seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace LatihanMVVM
{
    public class ItemPenjualan
    {
        public ItemPenjualan()
        {
            DiskonPersen = 0;
        }

        public long Id { get; set; }

        public string NamaBarang { get; set; }

        public int Jumlah { get; set; }

        public decimal Harga { get; set; }

        public decimal DiskonPersen { get; set; }

        public decimal Total()
        {
            decimal total = Jumlah * Harga;
            return total - (DiskonPersen / 100 * total);
        }
    }
}

Beberapa programmer menambahkan event pada model sehingga dapat di-ekspos langsung melalui view model. Walaupun membuat kode program view model nanti menjadi lebih sederhana, saya tidak terlalu suka dengan pendekatan seperti itu karena mengotori kode program model yang seharusnya hanya berisi business logic.

Berikutnya, saya perlu membuat view berdasarkan isi model. Itu sebabnya saya selalu mulai dengan membuat domain model karena ia adalah pemicu dari segala pengembangan berikutnya. View pada Windows Presentation Foundation (WPF) dibuat dalam bentuk XML yang disebut sebagai XAML. Sama seperti view pada Windows Forms, view XAML ini juga didukung oleh sebuah class C#.

Editor untuk WPF di Visual Studio 2010

Editor untuk WPF di Visual Studio 2010

Sebuah view harus dirancang agar sebisa mungkin hanya berisi tampilan UI (sesuai namanya). Hal ini tidak jadi masalah pada WPF karena view berupa XAML yang tidak mengizinkan adanya kode program. Tapi pada teknologi lain, seperti JSP atau PHP yang membolehkan adanya kode program, pemula yang tidak berhati-hati bisa saja melanggar ketentuan MVC dengan meletakkan kode program seperti data access logic atau business logic di view. Begitu juga dengan teman saya yang berkonsultasi. Ia memakai designer NetBeans untuk membuat aplikasi dengan UI Swing. Setelah mencoba penggunaan MVC, ia mengeluh kenapa tidak meletakkan beberapa logic yang seharusnya bisa langsung diletakkan di view misalnya di bagian actionPerformed di tombol. Kenapa harus memindahkan semua logic yang tidak berkaitan dengan view ke controller? Meletakkan segala sesuatunya di view adalah sesuatu yang tepat (dan dianjurkan!) untuk aplikasi sederhana. Tapi, tentu saja itu bukan lagi mengikuti pola MVC. Dan kita tidak sedang ingin menciptakan pola setengah MVC, bukan? Penggunaan pola yang standar akan mempermudah komunikasi tim dimana setiap anggota tim dapat memahami hasil kerjaan anggota tim lainnya dengan mudah. Cara sederhana untuk menguji apakah arsitektur kode program yang dibuat sudah ‘standar’ adalah dengan mempublikasikannya di forum diskusi dan melihat masukan dari programmer lain. Bila kode program tersebut sulit dimengerti atau dijauhi pengunjung, kemungkinan ada yang salah dengan arsitekturnya🙂

Saya mengubah kode program di MainWindow.xaml menjadi seperti berikut ini:

<Window x:Class="LatihanMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="356" Width="528">

    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="20" />
            <Setter Property="FontFamily" Value="Myriad Pro" />
            <Setter Property="FontWeight" Value="SemiBold" />
            <Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FF508FC4" Offset="0" />
                        <GradientStop Color="#FF6F94AD" Offset="1" />
                        <GradientStop Color="#FFC7F3FF" Offset="0.302" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
            <Setter Property="Foreground">
                <Setter.Value>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FF5252CE" Offset="0" />
                        <GradientStop Color="#FF0000DB" Offset="0.953" />
                        <GradientStop Color="#FF6363CB" Offset="0.337" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="Label">
            <Setter Property="FontSize" Value="14" />            
        </Style>

        <Style TargetType="TextBox">
            <Setter Property="Language" Value="in-IN" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border x:Name="customBorder" Background="{TemplateBinding Background}" CornerRadius="5" BorderThickness="2" BorderBrush="Gray">
                            <ScrollViewer x:Name="PART_ContentHost"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsKeyboardFocused" Value="True">                                
                                <Setter TargetName="customBorder" Property="Effect">
                                    <Setter.Value>
                                        <DropShadowEffect BlurRadius="10" ShadowDepth="0" Color="#578EC9"/>
                                    </Setter.Value>
                                </Setter>                                
                            </Trigger>
                            <Trigger Property="IsKeyboardFocused" Value="False">
                                <Setter Property="Foreground" Value="Gray" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="Button">
            <Setter Property="Background" Value="#DEF2FC" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect BlurRadius="10" ShadowDepth="0" Color="#578EC9"/>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border x:Name="customBorder" Background="{TemplateBinding Background}" CornerRadius="4" BorderThickness="2" BorderBrush="Gray">
                            <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" />
                        </Border>     
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#2394CC" />
                                <Setter Property="Foreground" Value="White" />                                
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">                                
                                <Setter Property="Effect" Value="{x:Null}" />
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Effect">
                                    <Setter.Value>
                                        <BlurEffect Radius="3"  />
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>                    
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <Grid>        
        <Label Content="Nama Barang:" Height="29" HorizontalAlignment="Left" Margin="0,49,0,0" Name="label2" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="107" />
        <TextBox Height="23" HorizontalAlignment="Stretch" Margin="112,55,12,0" Name="textBox1" VerticalAlignment="Top" />
        <Label Content="Jumlah:" Height="27" HorizontalAlignment="Left" Margin="1,86,0,0" Name="label3" VerticalAlignment="Top" Width="106" HorizontalContentAlignment="Right" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="113,90,0,0" Name="textBox2" VerticalAlignment="Top" Width="62" />
        <Label Content="Harga:" Height="28" HorizontalAlignment="Left" Margin="12,122,0,0" Name="label4" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="95" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="113,127,0,0" Name="textBox3" VerticalAlignment="Top" Width="124" />
        <Button Content="Simpan" Height="27" HorizontalAlignment="Left" Margin="207,228,0,0" Name="button1" VerticalAlignment="Top" Width="82" />
        <Label Content="Diskon (%):" Height="33" HorizontalAlignment="Left" Margin="12,161,0,0" Name="label5" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="95" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="113,165,0,0" Name="textBox4" VerticalAlignment="Top" Width="62" />
        <Label Content="Total:" Height="33" HorizontalAlignment="Left" Margin="12,194,0,0" Name="label6" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="95" />
        <Label Content="Label" Height="28" HorizontalAlignment="Left" Margin="113,194,0,0" Name="label7" VerticalAlignment="Top" Width="402" />
        <TextBlock Height="28" HorizontalAlignment="Stretch" Name="textBlock1" Text="Tambah Item Penjualan" VerticalAlignment="Top" TextAlignment="Center" Margin="0,12,0,0" />

        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFB7CEFF" Offset="0.192" />
                <GradientStop Color="White" Offset="1" />
                <GradientStop Color="#FF1648AD" Offset="0" />
            </LinearGradientBrush>
        </Grid.Background>

    </Grid>
</Window>

Salah satu kelebihan WPF adalah saya bisa melakukan pengaturan setiap komponen secara leluasa dengan menggunakan style dan templates. Sebagai contoh, saya bisa membuat Button dan TextBox memiliki ujung yang membulat. Selain itu, WPF juga menyediakan efek seperti blur dan shadow. Pada XAML di atas, definisi form saya terletak di bagian <Grid>. Saya memisahkan style dan templates ke <Window.Resources> sehingga saya bisa melakukan pengaturan secara global. Bagi yang terbiasa memakai HTML, hal ini ibarat mendeklarasikan CSS pada bagian <head> untuk mengendalikan tampilan di <body>. Bila saya menjalankan program, saya akan memperoleh tampilan seperti berikut ini:

Tampilan View

Tampilan View

XAML di WPF memiliki kemampuan setara atau lebih dari HTML5, bukan? Sesungguhnya Microsoft sudah ‘beralih hati’ ke HTML5 di Windows 8! Sebuah subset WPF untuk kebutuhan web, yaitu Silverlight, telah di-‘bunuh’ oleh Microsoft (tidak akan dibuat versi barunya lagi) dan digantikan dengan HTML5. Walaupun demikian, WPF dan XAML-nya masih tetap pilihan yang masuk akal untuk membuat aplikasi desktop dengan tampilan menarik.

Pekerjaan untuk merancang tampilan di XAML, sama seperti merancang halaman web, sebenarnya lebih tepat dikerjakan oleh designer. Microsoft dulu bahkan memiliki produk designer untuk XAML yang disebut sebagai Microsoft Expression Blend. Targetnya adalah untuk para designer yang kreatif. Kini, Expression Blend sudah tidak dijual terpisah lagi melainkan terintegrasi di Visual Studo 2012 menjadi Blend for Visual Studio 2012. Perbedaan antara designer dan programmer adalah designer belum tentu dapat membuat kode program. Mereka umumnya lebih senang dengan tool visual sejenis Blend atau Photoshop. Oleh sebab itu, view pada pola MVVM tidak perlu mengandung kode program sehingga designer menjadi lebih nyaman dalam menuangkan kreatifitas mereka.

MVVM terdiri atas model, view dan view model. Saya sudah membuat model dan view. Sekarang, saya akan membuat view model. Pada dasarnya, view model adalah sesuatu yang menampung state/nilai yang ada diview sehingga command (dan controller bila ada) tidak perlu mengakses setiap control yang ada di view secara langsung. Sebagai contoh, saya akan membuat sebuah class baru dengan nama ItemPenjualanViewModel yang isinya seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace LatihanMVVM
{
    class ItemPenjualanViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ItemPenjualan model;

        public ItemPenjualanViewModel(ItemPenjualan itemPenjualan = null)
        {
            this.model = itemPenjualan ?? new ItemPenjualan();
        }

        public string NamaBarang
        {
            get { return model.NamaBarang; }
            set
            {
                if (value != model.NamaBarang)
                {
                    model.NamaBarang = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("NamaBarang"));
                }
            }
        }

        public int Jumlah
        {
            get { return model.Jumlah; }
            set
            {
                if (value != model.Jumlah)
                {
                    model.Jumlah = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Jumlah"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));
                }
            }
        }

        public decimal Harga
        {
            get { return model.Harga; }
            set
            {
                if (value != model.Harga)
                {
                    model.Harga = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("Harga"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));
                }
            }
        }

        public decimal DiskonPersen
        {
            get { return model.DiskonPersen; }
            set
            {
                if (value != model.DiskonPersen)
                {
                    model.DiskonPersen = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("DiskonPersen"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Total"));
                }
            }
        }

        public string Total
        {
            get 
            {
                decimal? total = model.Total();
                if (!total.HasValue)
                {
                    return "-";
                }
                else
                {
                    return total.Value.ToString("C");
                }
            }
        }

        public ItemPenjualan Model
        {
            get { return this.model; }
        }
    }
}

Kode program view model di atas dikotori oleh kode program yang berhubungan dengan INotifyPropertyChanged. Tapi sayangnya ini adalah sebuah kebutuhan yang tidak dapat dihindari bila akan memakai binding. Untuk menyederhanakan kode program, saya dapat menggunakan framework atau membuat class infrastruktur. Sebagai contoh, Prism Library menyediakan class NotificationObject yang dapat dijadikan superclass untuk seluruh view model yang ada. Solusi yang paling elegan yang pernah saya temui adalah penggunaan annotation @Bindable yang kerap saya pakai di Griffon Framework (framework MVC untuk Groovy).

Teman saya yang sedang berkonsultasi mengutarakan pikirannya yang berbeda tentang view model. Saat mencoba memakai MVVM, ia menemukan banyak property yang sama di view model dan model. Lalu, ia mengambil sebuah jalan pintas: ia memakai model secara langsung tanpa membuat view model khusus pada beberapa screen yang sederhana. Walaupun hal ini tidak salah, solusi yang lebih konvensional adalah dengan tetap memakai view model yang hanya meng-ekspos satu property berisi model secara langsung ke view. Nantinya, view akan melakukan binding ke masing-masing property di model yang dibutuhkan. Solusi ini mensyaratkan agar model mengimplementasikan INotifyPropertyChanged. Itu sebabnya saya menghindari solusi ini: mengotori view model lebih baik daripada mengotori model🙂 Namun solusi ini jauh lebih baik daripada melakukan binding langsung ke model tanpa melalui view model. Hal ini karena seiring dengan pertumbuhan aplikasi, kemungkinan besar suatu hari nanti screen akan memiliki state yang tidak ada di model dan lebih tepat bila dimiliki view state. Sebuah view state adalah superset dari model: selain memiliki semua property milik model, ia juga umumnya memiliki property lainnya untuk kebutuhan view.

Langkah berikutnya, saya perlu menghubungkan antara view, view model dan model. Karena model sudah dibuat secara langsung di constructor view model, maka sekarang saya perlu menghubungkan view dan view model. Saya men-double click file MainWindow.xaml.cs dan mengubah kode program yang ada menjadi seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace LatihanMVVM
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ItemPenjualanViewModel();
        }
    }
}

Pada kode program di atas, saya menghubungkan view ke view model secara hardcoded. Ada sedikit rasa bersalah disini karena coupling-nya terlalu ‘tinggi’! Untuk solusi yang lebih baik, saya perlu menggunakan container yang menerapkan dependency injection. Kalau ini adalah Java, saya tidak akan berpikir dua kali untuk memakai Spring Framework. Lalu bagaimana dengan C#? Salah satu contoh dependency injenction container yang dapat dipakai di C# adalah Unity Container buatan Microsoft. Agar tetap sederhana, saya akan tetap melanjutkan dengan cara manual.

Salah satu ciri khas MVVM adalah perubahan pada view akan langsung diperbaharui pada view model dan begitu juga sebaliknya. Jadi, pada kode program, saya tidak perlu mengakses sebuah TextBox untuk mendapatkan nilai harga. Saya hanya perlu mengakses property Harga milik view model. Saya bahkan tidak perlu tahu apa komponen UI-nya, bisa saja bukan sebuah TextBox dan saya tidak peduli, karena yang saya butuhkan adalah nilai harganya! Agar hal ini terwujud, saya perlu melakukan apa yang disebut sebagai data binding. WPF sudah menyediakan fasilitas untuk melakukan binding. Sebagai contoh, saya mengubah MainWindow.xaml menjadi seperti berikut ini:

...
<Label Content="Nama Barang:" ... />
<TextBox Name="textBox1" ... Text="{Binding Path=NamaBarang}"/>

<Label Content="Jumlah:" ... />
<TextBox ... Text="{Binding Path=Jumlah, StringFormat={}{0:#,0}}"/>

<Label Content="Harga:" ... />
<TextBox ... Text="{Binding Path=Harga, StringFormat={}{0:C}}"/>                

<Label Content="Diskon (%):" ... />
<TextBox ... Text="{Binding Path=DiskonPersen, StringFormat={}{0:#.#}}"/>

<Label Content="Total:" ... />
<Label .... Content="{Binding Path=Total}" />
...

Cara melakukan binding di WPF boleh dibilang merupakan sesuatu yang mudah. Saya hanya perlu memberikan markup extension berupa Binding di property yang hendak di-bind. Sebagai contoh, pada kode program di atas, saya melakukan binding pada property Text milik TextBox untuk mengisi nama barang dengan property NamaBarang milik ItemPenjualanModelView selaku source-nya. Secara default, binding pada TextBox adalah two-way binding. Dengan demikian, setiap kali pengguna mengubah nilai TextBox tersebut , maka (setelah lost focus) nilai property ItemPenjualModelView.NamaBarang akan diperbaharui. Begitu juga sebaliknya, bila saya melakukan perubahan nilai ItemPenjualanModelView.NamaBarang di kode program, maka isi TextBox tersebut akan diperbaharui.

Menariknya, Binding memberikan keleluasaan untuk mengatur format dengan memakai property StringFormat. Saya juga dapat mendefinisikan nilai Converter bila perlu untuk menerjemahkan nilai secara manual. Untuk saat ini, saya secara otomatis sudah memakai type converter default yang akan menerjemahkan data angka menjadi string. Bila saya menjalankan program dan mengisi data, saya akan memperoleh hasil seperti pada gambar berikut ini:

Tampilan view setelah binding dengan view model

Tampilan view setelah binding dengan view model

Selain itu, sudah ada validasi bawaan bila saya memasukkan tipe data yang salah, seperti yang terlihat pada gambar berikut ini:

Tampilan view bila terjadi kesalahan konversi

Tampilan view bila terjadi kesalahan konversi

Bila garis kotak merah secara default terasa kurang indah, saya dapat mengubah view dibagian dimana saya mendeklarasikan style untuk TextBox menjadi seperti berikut ini:

...
<Style TargetType="TextBox">
   ...
   <Setter Property="Validation.ErrorTemplate">
      <Setter.Value>
         <ControlTemplate>
            <StackPanel Orientation="Horizontal">
               <AdornedElementPlaceholder />                                                            
               <TextBlock Text="Perlu diperbaiki!" Padding="3" Foreground="Red" />
            </StackPanel>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   ...
</Style>
...

Sesuai dengan konsep MVC atau MVVM, perubahan hanya perlu saya lakukan di view tanpa mempengaruhi model atau view model karena memang perubahan tersebut hanya berkaitan dengan tampilan. Bila terjadi kesalahan konversi data, saya akan memperoleh hasil seperti pada gambar berikut ini:

Kustomisasi notifikasi kesalahan di view

Kustomisasi notifikasi kesalahan di view

Berikutnya, saya perlu menyimpan model ke database. Ini adalah sebuah tugas yang jauh berbeda dari sebelumnya. Program sederhana di artikel ini, walaupun sudah bisa dijalankan dan bekerja, hanyalah sebatas tampilan. Pola MVVM memang dipakai untuk meng-implementasi-kan presentation layer. Menyimpan data ke database merupakan sesuatu yang diluar tanggung jawab MVC atau MVVM. Ia adalah tanggung jawab dari data access layer atau persistence layer. Pada .NET, saya dapat memakai Entity Framework untuk membuat persistence layer dengan mudah.

Saya sudah menuliskan bagaimana membuat persistence layer di artikel Memakai Entity Framework Di Visual Studio 2010. Seperti sebelumnya, saya mulai dengan men-install Entity Framework untuk proyek ini. Karena kali ini saya akan memakai database MySQL, saya perlu mengubah file App.config yang dihasilkan menjadi seperti berikut ini:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  ...
  <entityFramework>    
    <providers>
      <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />      
    </providers>    
  </entityFramework>
  <connectionStrings>
    <add name="LatihanContext" connectionString="server=localhost; database=latihan; uid=steven; password=12345" providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
</configuration>

Untuk memakai database MySQL dengan pendekatan code first di EF 6, saya minimal harus memakai Connector/.NET versi 6.8 yang dapat di-download di http://dev.mysql.com/downloads/connector/net/6.8.html. Setelah proses instalasi selesai, saya perlu menambahkan referensi ke Connector/.NET dengan men-klik kanan nama proyek dan memilih menu Add Reference…. Pada tab .NET, saya memilih MySql.Data.Entity for EF6 dan men-klik tombol OK.

Selain itu, saya juga menambahkan sebuah atribut di model agar nilai property Id dihasilkan secara otomatis oleh database (melalui auto number):

...
using System.ComponentModel.DataAnnotations.Schema;

namespace LatihanMVVM
{
    public class ItemPenjualan
    {

        ...

        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long Id { get; set; }

        [StringLength(50)]
        public string NamaBarang { get; set; }

        ...

    }
}

Bila Visual Studio 2010 komplain karena tidak dapat menemukan namespace untuk atribut di atas, saya perlu menjalankan proyek (atau men-compile) agar Visual Studio 2010 memperbaharui dependencies proyek.

Berikutnya saya akan membuat class persistence context yang isinya seperti berikut ini:

using System.Data.Entity;

namespace LatihanMVVM
{
    class LatihanContext : DbContext
    {
        public DbSet<ItemPenjualan> DaftarItemPenjualan { get; set; }
    }
}

Pada DbContext, saya memakai CreateDatabaseIfNotExists sebagai initializer sehingga Connector/.NET akan membuat tabel berdasarkan model bila tabel belum ada.

Khusus untuk MySQL, mungkin karena dukungan EF 6 yang masih awal, tabel __migrationhistory yang dihasilkan secara otomatis malah memiliki kolom yang terlalu besar untuk di-index. Dengan demikian, setiap kali menyimpan data, saya akan memperoleh pesan kesalahan berupa Specified key was too long; max key length is 767 bytes. Untuk mengatasi permasalahan ini, saya perlu menambahkan sebuah HistoryContext yang isinya seperti berikut ini:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.Migrations.History;
using System.Data.Common;
using System.Data.Entity;

namespace LatihanMVVM
{
    public class MyHistoryContext : HistoryContext
    {
        public MyHistoryContext(DbConnection dbConnection, string defaultSchema)
            : base(dbConnection, defaultSchema)
        {
        }

        protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<HistoryRow>().Property(p => p.MigrationId).HasMaxLength(100).IsRequired();
            modelBuilder.Entity<HistoryRow>().Property(p => p.ContextKey).HasMaxLength(200).IsRequired();
        }
    }

    public class ModelConfiguration : DbConfiguration
    {
        public ModelConfiguration()
        {
            SetHistoryContext("MySql.Data.MySqlClient", (c, s) => new MyHistoryContext(c, s));
        }
    }
}

Sekarang persistence layer atau data access layer sudah siap. Yang perlu saya lakukan adalah menghubungkan presentation layer ke data access layer. Proses penyimpanan ke database akan dilakukan bila pengguna men-klik tombol simpan. Sekarang, fokus pekerjaan sudah kembali lagi ke MVVM di presentation layer. Pertanyaannya adalah dimana meletakkan kode program yang berisi aksi yang dikerjakan saat tombol simpan di-klik pengguna?

Sebuah godaan besar yang kerap datang adalah dengan men-double click tombol simpan di designer dan membuat kode program di event handler yang dihasilkan. Ini adalah cara yang bertentangan dengan pola MVVM! Mengapa demikian? Karena kode program event handler tersebut masih merupakan bagian dari view. Secara ideal, sebuah view tidak boleh mengandung kode program. Bisa saja ia akan dikerjakan oleh designer yang tidak mengerti kode program. Oleh sebab itu, saya perlu meletakkan kode program di model view (atau di controller bila ada).

Salah satu cara yang disarankan adalah dengan menggunakan ICommand (sejenis Action di Java Swing). Tapi masalahnya adalah WPF tidak memiliki ICommand yang tepat untuk pola MVVM. Oleh sebab itu, framework MVVM seperti Prism biasanya memiliki implementasi ICommand yang dapat dipakai. Karena saya tidak memakai framework, maka saya perlu membuat sebuah implementasi ICommand secara manual. Perlu diingat bahwa pada kasus yang realistis, harus ada framework atau minimal implementasi sejenis DelegateCommand. Berikut ini adalah perubahan yang saya lakukan pada file ItemPenjualanViewModel.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows;

namespace LatihanMVVM
{
    public class ItemPenjualanViewModel : INotifyPropertyChanged
    {
    ...

        private ICommand simpanCommand;

        ...

        public ICommand SimpanCommand
        {
            get
            {
                if (this.simpanCommand == null)
                {
                    this.simpanCommand = new SimpanCommand(this);
                }
                return this.simpanCommand; 
            }
        }

    }


    public class SimpanCommand : ICommand
    {

        private ItemPenjualanViewModel viewModel;

        public SimpanCommand(ItemPenjualanViewModel viewModel)
        {
            this.viewModel = viewModel;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return viewModel.Model.Total() > 0;
        }

        public void Execute(object parameter)
        {
            using (var db = new LatihanContext())
            {
                db.Database.Log = Console.Write;
                db.DaftarItemPenjualan.Add(viewModel.Model);
                db.SaveChanges();
        MessageBox.Show("Data berhasil disimpan ke database");
            }
        }

    }
}

Pada ICommand di atas, method CanExecute() menentukan apakah aksi boleh dikerjakan atau tidak. Bila method ini mengembalikan nilai false, maka button akan di-disable sehingga pengguna tidak dapat men-klik. Method Execute() adalah kode program yang akan dikerjakan bila button di-click. Lagi-lagi saya melakukan hardcoding disini! Cara yang lebih baik adalah menempelkan presentation layer dan persistence layer melalui dependency injection container seperti Unity (mirip Spring Framework di Java).

Terakhir, saya perlu melakukan binding dari button ke ICommand yang saya buat. Untuk itu saya mengubah kode view menjadi seperti berikut ini:

...
<Button Content="Simpan" ... Command="{Binding SimpanCommand}"/>
...

Sekarang, bila saya menjalankan program, saya dapat melakukan eksekusi seperti pada gambar berikut ini:

Menyimpan objek ke database

Menyimpan objek ke database

Saya telah membuat sebuah aplikasi dimana presentation layer-nya memakai teknologi WPF dan pola MVVM. Persistence layer dari aplikasi ini memakai Entity Framework 6. Berikut ini adalah class diagram yang menunjukkan apa yang sudah saya buat sampai disini:

Class diagram

Class diagram

Perihal Solid Snake
I'm nothing...

2 Responses to Menerapkan MVVM Di Windows Presentation Foundation (WPF)

  1. Ping-balik: Melakukan Binding Collection Di WPF | The Solid Snake

  2. Hamdan Prakoso mengatakan:

    Membantu sekali buat semakin memahami MVVM, nice post !

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: