Tugas 4 PBKK - Menerapkan MVVM Pada Windows Presentation Foundation

Pada tugas kali ini, saya mencoba untuk menerapkan MVVM pada WPF (Windows Presentation Foundation).


Pendahuluan

MVVM adalah kependekan dari Model, View, ViewModel. Secara garis besar MVVM merupakan salah satu architectural patterns yang membagi tanggung jawab kepada tiga komponen utama yaitu Model, View, dan View-Model. 


Tanggung Jawab Masing - Masing Komponen

1. Model : Bertanggung jawab untuk menyediakan data yang di butuhkan oleh aplikasi.

2. View : Bertanggung jawab untuk merepresentasikan data yang berisi User Interface pada aplikasi.

3. ViewModel : Bertanggung jawab untuk menyimpan dan mengambil data dari Model untuk nantinya digunakan dan ditampilkan oleh View. Komponen ini merupakan inti dari Architectural Patterns MVVM.


Langkah - Langkah Pembuatan Aplikasi (mengikuti langkah pada referensi)

1. Memilih project WPF dengan menggunakan .NET Framework versi 5

2. Membuat class ItemPenjualan  sebagai model

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);
        }
    }
}    

3. Tambahkan reference System.ComponentModel.DataAnnotations pada solution aplikasi

4. Mengubah kode program pada MainWindow.xaml untuk membuat tampilan aplikasi

<Window ...

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

5. Membuat class ItemPenjualanViewModel sebagai viewmodel

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; }
        }
    }
}

6. Mengubah class MainWindow.xaml.cs untuk menghubungkan model dengan view

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();
        }
    }
}

7. Mengubah kode program pada MainWindow.xaml untuk melakukan binding dan menambahkan validation

...
<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}" />
...


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

8. Sebelum mengubah kode program pada MainWindow.xaml, kita perlu mendownload MySQL Connector/.NET versi 6. Disini saya menggunakan versi 6.8.7

9. Selanjutnya kita perlu menambahkan reference MySql.Data.Entity.EF6

10. Tambahkan Nuget Package Entity Framework 6

11. Mengubah kode program pada App.config

<?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>

12. Mengubah kode program pada ItemPenjualan.cs untuk 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; }
 
        ...
 
    }
}

13. Membuat class LatihanContext

using System.Data.Entity;
 
namespace LatihanMVVM
{
    class LatihanContext : DbContext
    {
        public DbSet<ItemPenjualan> DaftarItemPenjualan { get; set; }
    }
}

14. Membuat class MyHistoryContext

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));
        }
    }
}

15. Mengubah kode program dari class ItemPenjualanViewModel untuk menambah class SimpanCommand dan propertynya pada ViewModel

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");
            }
        }
 
    }
}

16. Mengubah kode program pada MainWindow.xaml untuk melakukan binding pada button simpan

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

17. Tambahkan kode pada LatihanContext.cs 

using System.Data.Entity;
 
namespace LatihanMVVM
{
    
   [DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
    class LatihanContext : DbContext
    {
        public DbSet<ItemPenjualan> DaftarItemPenjualan { get; set; }
    }
}

18. Buat migration database dengan cara membuka package manager console, lalu ketikkan Enable-Migrations dan Add-Migration dengan package name initial


19. Migrate database dengan mengetikkan Update-Database dan database kita akan otomatis ter-update


20. Jalankan program


Hasil

1. Tampilan awal



2. Tampilan ketika kita salah memasukkan data



3. Tampilan ketika data yang kita masukkan sudah benar



4. Tampilan setelah submit data yang sudah benar



Souce Code

https://github.com/KresnaAP/mvvm-practice-wpf


Referensi

https://thesolidsnake.wordpress.com/2014/01/10/menerapkan-mvvm-di-windows-presentation-foundation-wpf/



Comments

Popular posts from this blog

Tugas 2 PBKK - Aplikasi .NET Framework

Tugas 8 PBKK - Company Profile Menggunakan Laravel

Tugas 1 PBKK - Portofolio Aplikasi Framework