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:
Loan
.Loan
.Loan
data source to Details.Loan[]
containing one object.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:
The application:
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 TextBlock
s and 3 TextBox
s 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:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With