Friday 4 March 2011

A day of WPF paired programming

Here’s a journal of code that was written in a paired programming session last week…

We started by looking at the FranchiseStepPayoutFunction Control. This control has some interesting behavior. Part of the control consists of a table of LowerLimit,UpperLimit and Payout. We would like that the Upper Limit of Row n populates the lower limit of Row n+1. We would also like to style the grid with characters like "-" between the upper and lower limit and a % for the payout.

Starting with the xaml, we decided to use a ItemControl to solve this:

        <ItemsControl ItemsSource="{Binding StepPayouts}" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="4">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                        <TextBox Text="{Binding LowerStepLimit, Mode= TwoWay}" Width="100" Margin="0 0 10 0" />
                        <Label Content="-" Height="23" VerticalAlignment="Top" />
                        <TextBox Text="{Binding UpperStepLimit, Mode= TwoWay}" Width="100" Margin="0 0 10 0"/>
                        <TextBox Text="{Binding Payout, Mode= TwoWay}" Width="100" />
                        <Label Content="%" Height="23" VerticalAlignment="Top" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

This is bound to a List

    public class FranchiseStepPayouts : ModelSettingsBase
    {
        public List<StepPayout> StepPayouts { get; private set; }

        public FranchiseStepPayouts()
        {
            StepPayouts = new List<StepPayout>();

            // Populate 4 columns
            for (int i = 0; i < 4; i++)
            {
                StepPayout s = new StepPayout(0, 0, 0);
                s.UpperStepLimitChangedEvent += new StepPayout.UpperStepLimitChangedHandler(s_UpperStepLimitChangedEvent);
                StepPayouts.Add(s);
            }
        }

...
    }

The StepPayout looks like:

namespace Ccp.Wpf.Infrastructure.Entities
{
    public class StepPayout : NotificationObject
    {

        public delegate void PayoutChangedHandler(object sender);
        public event PayoutChangedHandler PayoutChangedEvent;
        virtual protected void PayoutChangedRaiseEvent()
        {
            if (PayoutChangedEvent != null)
            {
                PayoutChangedEvent(this);
            }
        }

        private double payout;
        public double Payout
        {
            get
            {
                return this.payout;
            }
            set
            {
                payout = value;
                RaisePropertyChanged(()=>Payout);
                PayoutChangedRaiseEvent();
            }
        }
        .. dito for Lower and Upper Step Payout limits

        public StepPayout(double UpperStepLimit, double LowerStepLimit, double Payout)
        {
            this.lowerStepLimit = LowerStepLimit;
            this.upperStepLimit = UpperStepLimit;
            this.payout = Payout;
        }
    }
}

Notice that we used a home made NotificationObject, this is so that we don't need to write a string in the RaisePropertyChanged field. Which means, instead of writing
                RaisePropertyChanged("Payout");
we can write
                RaisePropertyChanged(()=>Payout);

We copied the code that makes this possible from Prism. Our home made class for this looks like:

namespace Ccp.Wpf.Infrastructure.Helpers
{
    public class NotificationObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            var propertyName = ExtractPropertyName(propertyExpression);
            this.RaisePropertyChanged(propertyName);
        }

        public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
        {
            if (propertyExpression == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }
            var memberExpression = propertyExpression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("NotMemberAccessExpression_Exception", "propertyExpression");
            }
            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("ExpressionNotProperty_Exception", "propertyExpression");
            }
            var getMethod = property.GetGetMethod(true);
            if (getMethod.IsStatic)
            {
                throw new ArgumentException("StaticExpression_Exception", "propertyExpression");
            }
            return memberExpression.Member.Name;
        }
    }
}

Our application does not need to be localizable so we removed the references to resources.

The next thing that we looked at was the list that we used in the view model

    public class FranchiseStepPayouts : ModelSettingsBase
    {
        public List<StepPayout> StepPayouts { get; private set; }

This works well provided the number of items in the list does not change. If the number of items in the list changes you need to replace List with ObservableCollection and you need to RaisePropertyChanged so that the UI know it needs to update. If the list was very large this would lead to an avalanche of events that would make the UI unresponsive, in that case it is better to create the observable list and set the entire property in one go. In our case we only have a few items. This makes the code look like the following.

    public class FranchiseStepPayouts : ModelSettingsBase
    {

        private ObservableCollection<StepPayout> stepPayouts;
        public ObservableCollection<StepPayout> StepPayouts
        {
            get
            {
                return stepPayouts;
            }
            private set
            {
                stepPayouts = value;
                RaisePropertyChanged(() => StepPayouts);
            }
        }

        private PayOutFunction payoutFunction;

        public PayOutFunction PayoutFunction
        {
            get
            {
                return payoutFunction;
            }
            set
            {
                payoutFunction = value;
                RaisePropertyChanged(() => PayoutFunction);
            }
        }

We register the events in the constructor

        public FranchiseStepPayouts()
        {
            StepPayouts = new ObservableCollection<StepPayout>();

            // Populate 4 columns
            for (int i = 0; i < 4; i++)
            {
                StepPayout s = new StepPayout(0, 0, 0);
                s.UpperStepLimitChangedEvent += new StepPayout.UpperStepLimitChangedHandler(s_UpperStepLimitChangedEvent);
                StepPayouts.Add(s);
            }
        }

We respond to the events by replacing the lower limit of the n+1 column
        void s_UpperStepLimitChangedEvent(object sender)
        {
            for (int i = 0; i < this.StepPayouts.Count - 1; i++)
            {
                this.StepPayouts[i + 1].LowerStepLimit = this.StepPayouts[i].UpperStepLimit;
            }
        }

Next adding an auto hide function with checkbox

    public class FranchiseStepPayouts : ModelSettingsBase
    {

        private bool isPayoutFunctionVisible;

        public bool IsPayoutFunctionVisible
        {
            get
            {
                return isPayoutFunctionVisible;
            }
            set
            {
                isPayoutFunctionVisible = value;
                RaisePropertyChanged(() => IsPayoutFunctionVisible);
            }
        }

<UserControl x:Class="Ccp.Wpf.Infrastructure.Controls.FranchiseStepPayoutsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Ccp.Wpf.Infrastructure.Controls"
             xmlns:converter="clr-namespace:Ccp.Wpf.Infrastructure.Converter"
             mc:Ignorable="d"
             d:DesignWidth="500">
    <UserControl.Resources>
        <local:EnumMatchToBooleanConverter x:Key="enumConverter" />
    </UserControl.Resources>

    <StackPanel Orientation="Vertical" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
        <CheckBox Content="Japanise Payout Function" IsChecked="{Binding IsPayoutFunctionVisible}"/>
        <GroupBox Header="Japanise Payout Function" Visibility="{Binding IsPayoutFunctionVisible, Converter={converter:BoolToVisibilityConverter FalseValue=Collapsed}}" VerticalAlignment="Top" HorizontalAlignment="Left">
            <Grid>

                <!-- Content -->

            </Grid>
        </GroupBox>
    </StackPanel>
</UserControl>

namespace Ccp.Wpf.Infrastructure.Converter
{
    public class BoolToVisibilityConverter : MarkupExtension, IValueConverter
    {
        public BoolToVisibilityConverter()
        {
            TrueValue = Visibility.Visible;
            FalseValue = Visibility.Collapsed;
        }

        public Visibility TrueValue { get; set; }
        public Visibility FalseValue { get; set; }

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool val = System.Convert.ToBoolean(value);
            return val ? TrueValue : FalseValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return TrueValue.Equals(value) ? true : false;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}

Next here is the implementation of a radio button

<UserControl x:Class="Ccp.Wpf.Infrastructure.Controls.FranchiseStepPayoutsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Ccp.Wpf.Infrastructure.Controls"
             xmlns:converter="clr-namespace:Ccp.Wpf.Infrastructure.Converter"
             mc:Ignorable="d"
             d:DesignWidth="500">
    <UserControl.Resources>
        <local:EnumMatchToBooleanConverter x:Key="enumConverter" />
    </UserControl.Resources>
...

                <StackPanel Orientation="Vertical"  HorizontalAlignment="Left"  Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Width="100">
                    <StackPanel Orientation="Horizontal"  HorizontalAlignment="Left"  >
                        <RadioButton Content="Franchise" GroupName="Franchise" Height="23"
                             IsChecked="{Binding Path=PayoutFunction, Mode=TwoWay,
                                                 Converter={StaticResource enumConverter},
                                                 ConverterParameter=Franchise}"  />
                        <Label Content=" " Height="23" VerticalAlignment="Top" />
                    </StackPanel>
                    <StackPanel Orientation="Horizontal"  HorizontalAlignment="Left"  >
                        <RadioButton Content="Step Payout" GroupName="Franchise" Height="23"
                             IsChecked="{Binding Path=PayoutFunction, Mode=TwoWay,
                                                 Converter={StaticResource enumConverter},
                                                 ConverterParameter=StepPayout}"  />
                        <Label Content=" " Height="23" VerticalAlignment="Top" />
                    </StackPanel>
                </StackPanel>
...

            </Grid>
        </GroupBox>
    </StackPanel>
</UserControl>

namespace Ccp.Wpf.Infrastructure.Controls
{
    public class EnumMatchToBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            if (value == null || parameter == null)
                return false;

            string checkValue = value.ToString();
            string targetValue = parameter.ToString();
            return checkValue.Equals(targetValue,
                     StringComparison.InvariantCultureIgnoreCase);
        }

        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            if (value == null || parameter == null)
                return null;

            bool useValue = (bool)value;
            string targetValue = parameter.ToString();
            if (useValue)
                return Enum.Parse(targetType, targetValue);

            return null;
        }
    }
}

namespace Ccp.Wpf.Infrastructure.Entities
{
    public enum PayOutFunction
    {
        None,
        Franchise,
        StepPayout
    }
}

namespace Ccp.Wpf.Infrastructure.Entities
{
    public class FranchiseStepPayouts : ModelSettingsBase
    {
        private PayOutFunction payoutFunction;

        public PayOutFunction PayoutFunction
        {
            get
            {
                return payoutFunction;
            }
            set
            {
                payoutFunction = value;
                RaisePropertyChanged(() => PayoutFunction);
            }
        }

Next here is how to call the wpf windows with a foriegn key (ExposureDataId)

Use the container to access the modelview of the WPF user control

Public Class frmCatFocusMain
    Implements IDisposable
..
   Private _container As CompositionContainer
..
    Public Sub New()

        'Dim directoryCatalog As New DirectoryCatalog(".")
        Dim iAggregateCatalog As New AggregateCatalog()

        iAggregateCatalog.Catalogs.Add(New AssemblyCatalog(Assembly.GetExecutingAssembly()))
        iAggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(ViewFactory).Assembly))
        iAggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(ModelSettingsService).Assembly))
        iAggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(ModelSettingsForEarthquakeAfricaIndia).Assembly))
        'iAggregateCatalog.Catalogs.Add(New AssemblyCatalog(GetType(CommonModelSettingsView).Assembly))

        Dim batch As New CompositionBatch()
        batch.AddPart(Me)
        _container = New CompositionContainer(iAggregateCatalog)

        ViewFactory = New ViewFactory(_container)
        _container.ComposeExportedValue(Of ViewFactory)(ViewFactory)
        _container.ComposeParts(ViewFactory)
        _container.Compose(batch)
...
        InitializeComponent()
     End Sub
...
    Private Sub BtnNewRunModelWizard_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnNewRunModelWizard.Click
        Dim ExposureDataId As Integer = 12123
        Dim iWindow As Windows.Window = ViewFactory.CreateWindow(Of ModelSettingsView)()
        Dim viewModel As ModelSettingsViewModel = _container.GetExportedValueOrDefault(Of ModelSettingsViewModel)()
        viewModel.ExposureDataId = ExposureDataId
        iWindow.ShowDialog()
    End Sub

namespace Ccp.UI.ModelResults.Views.ModelSettings
{
    [Export(typeof(ModelSettingsViewModel))]
    //[PartCreationPolicy(CreationPolicy.NonShared)]
    public class ModelSettingsViewModel : ViewModelBase
    {
        private int exposureDataId;
        public int ExposureDataId
        {
            get
            {
                return exposureDataId;
            }
            set
            {
                exposureDataId = value;
                RaisePropertyChanged("ExposureDataId");
            }
        }

Next we populate the comboboxs by joining some tables using our POCO EF DAL with Linq then creating an observablecollection from the result

                var result = from av in this.databaseContext.GetAll<CatModel>()
                                 .Where(z => z.PerilId.Value.Equals(selectedModelPeril.PERIL_ID))
                                 .Where(z => ListOfImportedCatModels.Contains(z.Id))
                             join vf in this.databaseContext.GetAll<ModelRegion>()
                             on av.RegionId equals vf.RegionId
                             select vf;

                ModelRegions = new ObservableCollection<ModelRegion>(result);

Here's how joining the Lazy loaded collection of controls (via MEF) with meta data containing an enumeration that has a foriegn key to the corresponding model in our database. This is done within a setter….

                selectedModelRegion = value;
                CatModel Selected = databaseContext
                                    .Query<CatModel>()
                                    .Where(p => p.RegionId == selectedModelRegion.RegionId)
                                    .Where(p => p.PerilId == selectedModelPeril.PERIL_ID)
                                    .FirstOrDefault<CatModel>();

                var result = (from av in this.ModelSettings
                                 .Where(z => (int)z.Metadata.ModelType == Selected.Id)
                             select av).FirstOrDefault();

                SelectedModelSetting = result;

                RaisePropertyChanged("SelectedModelRegion");

Finally here is how we persist data

namespace Ccp.Contracts.Model
{
    public abstract class ModelSettingsBase : NotificationObject
    {
        public string Name
        {
            get { return this.GetType().Name; }
        }

        public abstract void Save(int ModelRunId);
        public abstract bool Validate();
    }
}

namespace Ccp.UI.ModelResults.Views.ModelSettings
{
    [Export(typeof(ModelSettingsViewModel))]
    //[PartCreationPolicy(CreationPolicy.NonShared)]
    public class ModelSettingsViewModel : ViewModelBase
    {
        public RelayCommand<ModelSettingsBase> SaveModelSettingsCommand
        {
            get
            {
                if (saveModelSettingsCommand == null)
                {
                    saveModelSettingsCommand = new RelayCommand<ModelSettingsBase>((ms) =>
                    {
                        // Save
                        int ModelRunId = 123;

                        ms.Save(ModelRunId);
                    }
                    , (ms) =>
                    {
                        // CanSave
                        return ms != null && ms.Validate();
                    });
                }

                return saveModelSettingsCommand;
            }
        }

namespace Ccp.Wpf.Infrastructure.Entities
{
    [CatModelExport(ModelType.EarthquakeAfricaIndia, typeof(ModelSettingsBase))]
    public class ModelSettingsForEarthquakeAfricaIndia : ModelSettingsBase
    {
        public CreditDeductible CreditDeductible { get; set; }
        public ZonalDeductibleByLOB ZonalDeductible3 { get; set; }

        public ModelSettingsForEarthquakeAfricaIndia()
        {
            CreditDeductible = new CreditDeductible();
            ZonalDeductible3 = new ZonalDeductibleByLOB();
        }
        /// <summary>
        /// Dummy implementation
        /// </summary>
        public override void Save(int ModelRunId)
        {
            CreditDeductible.Save(ModelRunId);
            ZonalDeductible3.Save(ModelRunId);
        }

        public override bool Validate()
        {
            return CreditDeductible.Validate() && ZonalDeductible3.Validate();
        }
    }
}

namespace Ccp.Wpf.Infrastructure.Entities
{
    public class CreditDeductible : ModelSettingsBase
    {
        public double Limit { get; set; }
        public double Deductible { get; set; }

        public override void Save(int ModelRunId)
        {
            // Update database here
        }