Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the WPF answer to this?

Tags:

winforms

wpf

I have used WPF to develop two moderately sized applications. I was much impressed by the cleanness of WPF and its features. When I explained to one of my colleagues (Who happens to develop business apps) the various benefits of WPF, he challenged me with this problem which had me totally stumped:

The Problem:

He coded an application in the following way in about 2 minutes:

  1. Open a new WinForms project.
  2. Define a class Loan.
  3. Build project.
  4. Define an object data source using Loan.
  5. In Data Sources explorer, change view type of the Loan data source to Details.
  6. Drag the data source onto the Form in the designer.
  7. Supply the data source with a Loan[] containing one object.
  8. Build and run application.

The code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WinForms_DataBinding_Example
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            loanBindingSource.DataSource = new Loan[] { new Loan() };
        }
    }

    public class Loan
    {
        public decimal Amount { get; set; }
        public decimal Rate { get; set; }
        public decimal Total { get { return Amount * Rate; } }
    }
}

The designer:

enter image description here

The application:

enter image description here

Now whenever you change the value of Amount or Rate in the window, the value of Total changes accordingly. After explaining that this is a very useful feature in business apps where any changes you make to one property in an entity immediately updates the view where calculated properties are refreshed instantly making the user experience better. Considering that the typical business entity class has a lot of properties, this saves a lot of coding. Then he asked me to do the same in WPF.

I first explained to him that I do not understand what sort of black magic goes on here. How does the Total textbox update itself automatically? This is my first question:

Q1. The Loan class does not implement INotifyPropertyChanged or something similar. So how does the Total textbox get updated when the Amount or Rate textboxes lose focus?

Then I told him that I do not know how to do the same thing so easily in WPF. However, I wrote the same app in WPF with 3 TextBlocks and 3 TextBoxs in the UI. I also needed to make Loan class implement INotifyPropertyChanged. Added backing fields to Amount and Rate. Whenever these properties were being set, I raised a property changed notification for the property Total. In the end, I was left with an app with badly aligned controls which did the same thing as the WinForms app. However, this was way harder to do than the WinForms method.

I came home and then had the bright idea of drag-dropping the Loan data source on to the WPF window (After I changed the view mode to detail). Sure enough, I got the same kind of UI as in WinForms app and after setting the data source to the same Loan[] as in WinForms app, it seemed to be complete. I ran the app, changed the Amount and Rate fields hoping to see Total change itself automagically. However, I was disappointed. The Total field did not change:

enter image description here

The code:

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;
using WinForms_DataBinding_Example;

namespace WPF_Grid_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded_1(object sender, RoutedEventArgs e)
        {

            System.Windows.Data.CollectionViewSource loanViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("loanViewSource")));
            // Load data by setting the CollectionViewSource.Source property:
            loanViewSource.Source = new List<Loan>() { new Loan() };
        }
    }
}

The xaml:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:WinForms_DataBinding_Example="clr-namespace:WinForms_DataBinding_Example;assembly=WinForms_DataBinding_Example" mc:Ignorable="d" x:Class="WPF_Grid_Example.MainWindow"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded_1">
    <Window.Resources>
        <CollectionViewSource x:Key="loanViewSource" d:DesignSource="{d:DesignInstance {x:Type WinForms_DataBinding_Example:Loan}, CreateList=True}"/>
    </Window.Resources>
    <Grid>
        <Grid x:Name="grid1" DataContext="{StaticResource loanViewSource}" HorizontalAlignment="Left" Margin="121,123,0,0" VerticalAlignment="Top">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Label Content="Amount:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="0" VerticalAlignment="Center"/>
            <TextBox x:Name="amountTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="3" Grid.Row="0" Text="{Binding Amount, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" VerticalAlignment="Center" Width="120"/>
            <Label Content="Rate:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="1" VerticalAlignment="Center"/>
            <TextBox x:Name="rateTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="3" Grid.Row="1" Text="{Binding Rate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" VerticalAlignment="Center" Width="120"/>
            <Label Content="Total:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="2" VerticalAlignment="Center"/>
            <TextBox x:Name="totalTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="3" Grid.Row="2" Text="{Binding Total, Mode=OneWay}" VerticalAlignment="Center" Width="120"/>
        </Grid>

    </Grid>
</Window>

Q2. I was confounded before by the black magic of WinForms, I was confounded now because the same black magic did not work in WPF. Why?

Q3. How do I make the WPF version to update the Total field automatically as in the WinForms example?

Q4. Which platform is better/faster for this sort of business app development? If I am to make a better argument on behalf of WPF, what should I be looking at?

I hope I was clear about the problem. Please let me know if any clarifications are needed. Thanks.

like image 686
nakiya Avatar asked Feb 13 '13 16:02

nakiya


2 Answers

Q1: If you look at the designer file for the Windows Form you'll see about 300 lines of code generated for your 3 textboxes. Some of this code is similar to:

this.amountTextBox.DataBindings.Add(
    new System.Windows.Forms.Binding("Text", 
        this.loanBindingSource, "Amount", true));

The Binding and the BindingSource co-operate to update the bound values and cause all bound controls to be updated every time one of the values changes (using reflection).

Q2: Because the WPF designer doesn't create a .Designer.cs file and the associated mess of code. You need to explicitly implement INotifyPropertyChange, which can be simplified by using say MVVM Light's ViewModelBase, e.g.

public class Loan : ViewModelBase
{
    public decimal Amount
    {
        get
        {
            return this.amount;
        }
        set
        {
            if (Set(() => Amount, ref this.amount, value))
            {
                RaisePropertyChanged(() => Total);
            }
        }
    }

Q3: 1) When Amount or Rate changes raise the property change notification for that property but also for the computed property 'Total'. 2) Modify your bindings on Amount and Rate to Binding="{Binding Amount, UpdateSourceTrigger=LostFocus}"

Q4: WPF no question (IMHO). The WPF way is more testable and maintainable and understandable.

like image 94
Phil Avatar answered Sep 28 '22 19:09

Phil


Answer to Q4:

Regardless of winforms having the ability to generate 3 silly textboxes for a class, WPF is a much better, scalable and powerful framework. It has much greater performance due to hardware acceleration and whatnot, and requires less or no code to do some tasks that take tons of code in winforms, such as this, or this:

<CheckBox x:Name="chk"/>
<TextBox IsEnabled="{Binding IsChecked,ElementName=chk}"/>

Also, tipical Line-of-Business applications have to deal with thousands or hundreds of thousands of records, and UI Virtualization makes a huge difference.

The bottom line is that winforms, regardless of having some designer goodies (which are more a feature of Visual Studio than winforms itself), is nowhere near as practical and adequate when it comes to Line of Business.

like image 42
Federico Berasategui Avatar answered Sep 26 '22 19:09

Federico Berasategui