Random programming things I'd want to remember

Saturday, August 29, 2015

Using MVVM to update a property on an object.



Alright, so a fresh new solution. Adding folders: Models, ServiceClasses, ViewModels, and our view should technically go into the Views folder, but I'll make it simple for this examle and use the default MainPage.xaml that already exists in the root folder of the solution.

Adding the model class, Model.cs into the /Models folder. For the simplicity, there are only two properties.
using System;
using System.ComponentModel;

namespace PWApp.Models
{
    public class Model1
    {

        public string PropertyString { get; set; }
        public int PropertyInt { get; set; }
    }
}


Next, let's add these classes into /ServiceClasses folder:

using System;
using System.Windows.Input;

namespace PWApp.ServiceClasses
{
    public class DelegateCommand : ICommand
    {
        Func<object, bool> canExecute;
        Action<object> executeAction;

        public DelegateCommand(Action<object> executeAction)
            : this(executeAction, null) { }

        public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
        {
            if (executeAction == null)
            {
                throw new ArgumentNullException("executeAction");
            }
            this.executeAction = executeAction;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter) //can command execute in its current status?
        {
            bool result = true;
            Func<object, bool> canExecuteHandler = this.canExecute;
            if (canExecuteHandler != null)
            {
                result = canExecuteHandler(parameter);
            }
            return result;
        }

        public event EventHandler CanExecuteChanged; //occurs when changes occur that affect whether or not the command should execute

        public void RaiseCanExecuteChanged()
        {
            EventHandler handler = this.CanExecuteChanged;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
        }

        public void Execute(object parameter) //Method to call when the command is invoked
        {
            this.executeAction(parameter);
        }
    }
}



Class "ShowConverter" is what we will use to show or hide the data based on their values. If it is not clear now how it works, it'll become all clear in the end. Something worth noticing here is that we are extracting the "value" parameter and converting it to an integer, and we are doing the same thing to the "parameter" object. If the values are the same, then whatever we are tying up to this is visible, otherwise it is hidden.

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace PWApp.ServiceClasses
{
    public class ShowConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            int i = System.Convert.ToInt32(value);
            int j = System.Convert.ToInt32(parameter);

            if (i == j) return Visibility.Visible;

            return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}


Next, adding items to the /ViewModels folder. Starting with the BaseViewModel class, it implements the events that we could use for all the other ViewModels

using System;
using System.ComponentModel;

namespace PWApp.ViewModels
{
    public class BaseViewModel : INotifyPropertyChanged
    {        
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}


Now, the actual ViewModel. This is where the magic is happening.

using System;
using System.Windows.Input;
using PWApp.Models;
using PWApp.ServiceClasses;
using PWApp.ViewModels;

namespace PWApp.ViewModels
{
    public class PWViewModel : BaseViewModel
    {
        public PWViewModel()
        {
            m1 = new Model1();
            m1.PropertyInt = 1;
            m1.PropertyString = "String 1";

            _alterM1Command = new DelegateCommand(this.AlterM1CommandAction, this.CanAlterM1); //add this line to wherever you initialize your commands
        }

        private Model1 m1;
        public Model1 M1
        {
            get { return m1; }
            set
            {
                if (m1 == value) return;
                m1 = value;
                OnPropertyChanged("M1");
            }
        }

        DelegateCommand _alterM1Command;
        public ICommand AlterM1Command { get { return _alterM1Command; } }
        private void AlterM1CommandAction(object obj)
        {
            int i = M1.PropertyInt;

            Model1 backup = new Model1 { PropertyInt = M1.PropertyInt, PropertyString = M1.PropertyString };
            //I am creating a whole new copy of the object because just changing the property does not change
            //the value of the object, and does not fire the OnPropertyChanged event
            if (++i == 3)
                M1 = new Model1 { PropertyInt = 0, PropertyString = backup.PropertyString };
            else
                M1 = new Model1 { PropertyInt = i, PropertyString = backup.PropertyString };
        }

        private bool CanAlterM1(object obj)
        {
            return true;
        }
    }
}



Now, the view. The actual face of the program. First thing to notice is the xmlns:service line in the Page tag. That declares the namespace that we can use, and we declare it to use the converter. The next line is Page.Resources tag, actually what's happening inside it. First declaration is me being lazy -- I set the FontSize of all the TextBoxes at once to 24. The second item is our declaration of the ShowConverter. I declared the namespace in the Page tag to be able to access the file, I actually tell the system what it is under the Resources section. I actually use the file to toggle visibility of an item in the following manner:

Visibility="{Binding M1.PropertyInt, Converter={StaticResource sc}, ConverterParameter=0}"


Here is the entire view. If you copy it into your app, don't forget to replace the "PWApp" namespace with your own.

<Page
    x:Class="PWApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PWApp"
    xmlns:service="using:PWApp.ServiceClasses"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="24" />
        </Style>
        <service:ShowConverter x:Key="sc" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Button Command="{Binding AlterM1Command}" Content="Alter M1" Margin="20, 60, 12, 12" />
        <Button Command="{Binding AlterM2Command}" Content="Alter M2" Grid.Column="1" Margin="12, 60, 12, 12" />
        <Button Command="{Binding AlterM3Command}" Content="Alter M3" Grid.Column="2" Margin="12, 60, 12, 12" />
        <TextBlock Text="M1:" Grid.Row="1" Margin="12"/>
        <Border BorderBrush="Red" Grid.Row="1" Grid.Column="1" BorderThickness="2" Padding="10" Margin="12"><TextBlock Text="{Binding M1.PropertyInt}" Margin="12" 
                   Visibility="{Binding M1.PropertyInt, Converter={StaticResource sc}, ConverterParameter=0}" /></Border>
        <Border BorderBrush="Red" Grid.Row="1" Grid.Column="2" BorderThickness="2" Padding="10" Margin="12"> <TextBlock Text="{Binding M1.PropertyInt}" Margin="12"
                   Visibility="{Binding M1.PropertyInt, Converter={StaticResource sc}, ConverterParameter=1}" /></Border>
        <Border BorderBrush="Red" Grid.Row="1" Grid.Column="3" BorderThickness="2" Padding="12" Margin="12"><TextBlock Text="{Binding M1.PropertyInt}" Margin="12"
                   Visibility="{Binding M1.PropertyInt, Converter={StaticResource sc}, ConverterParameter=2}" /></Border>
        <TextBlock Text="{Binding M1.PropertyString}" Grid.Row="1" Grid.Column="4" Margin="12" />
    </Grid>
</Page>


Finally, setting the DataContext of the MainPage, tying up the View to the ViewModel:

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.DataContext = new PWViewModel();
        }
    }