All in One WPF Demo

This is the demo we want to build up. It leverages all the awesome XAML and WPF features in one comprehensive demo. The data models represented in this App

This blog will take a few days to build out. Once we are done, you will be in good shape to apply XAML in your own WPF projects.

image

image

If you click the “Add Product” button, you get this interface.

  image

A lot of great stuff to learn

Look for “download sample” to get source code.

  image

Step 1 – Create a new WPF Project

The project new dialog box looks like this. Notice we are creating a “WPF Application”

image

So you’ll end up with a nice project as follows:

image

 

Step 2 – Load some data before displaying the “Window” object

Instead of making our startup object be a window, we will run some code instead. The code will load data. Next, the code will show the window. That way some data in memory for the window to display.

image 

Step 2 (continued) – Load some data

Two methods have been added below. The first method is “AppStartup” and the second method is “LoadAuctionData.” These methods have been added in App.xaml.cs

    1:      public partial class App : Application
    2:      {
    3:          void AppStartup(object sender, StartupEventArgs args)
    4:          {
    5:              LoadAuctionData();
    6:              MainWindow mainWindow = new MainWindow();
    7:              mainWindow.Show();
    8:   
    9:          }
   10:          private void LoadAuctionData()
   11:          {
   12:              
   13:          }
   14:      }

Next, we need to go to App.xaml and set the “Startup” to “AppStartup.”

image

Here is what the new code looks like.

    1:  <Application x:Class="MyDataBinding.App"
    2:      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3:      xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    4:      Startup="AppStartup">
    5:      <Application.Resources>
    6:           
    7:      </Application.Resources>
    8:  </Application>

You may want to step through the debugger to test it.

The startup code does two things. It populates some data structures with data. It then displays a window so we can observe those objects in memory.

image

Step 3 – Add 3 class modules

Right mouse click on the project and “Add/Class”

image

image

     public class Bid
     {
         private int amount;
         private User bidder;
  
         #region Property Getters and Setters
         public int Amount
         {
             get { return this.amount; }
         }
  
         public User Bidder
         {
             get { return this.bidder; }
         }
         #endregion
  
         public Bid(int amount, User bidder)
         {
             this.amount = amount;
             this.bidder = bidder;
         }
     }

image 

     public class AuctionItem : INotifyPropertyChanged
     {
         private string description;
         private int startPrice;
         private DateTime startDate;
         private ProductCategory category;
         private User owner;
         private SpecialFeatures specialFeatures;
         private ObservableCollection<Bid> bids;
  
         public event PropertyChangedEventHandler PropertyChanged;
  
         #region Properties Getters and Setters
         public string Description
         {
             get { return this.description; }
             set
             {
                 this.description = value;
                 OnPropertyChanged("Description");
             }
         }
  
         public int StartPrice
         {
             get { return this.startPrice; }
             set
             {
                 if (value < 0)
                 {
                     throw new ArgumentException("Price must be positive");
                 }
                 this.startPrice = value;
                 OnPropertyChanged("StartPrice");
                 OnPropertyChanged("CurrentPrice");
             }
         }
  
         public DateTime StartDate
         {
             get { return this.startDate; }
             set
             {
                 this.startDate = value;
                 OnPropertyChanged("StartDate");
             }
         }
  
         public ProductCategory Category
         {
             get { return this.category; }
             set
             {
                 this.category = value;
                 OnPropertyChanged("Category");
             }
         }
  
         public User Owner
         {
             get { return this.owner; }
         }
  
         public SpecialFeatures SpecialFeatures
         {
             get { return this.specialFeatures; }
             set
             {
                 this.specialFeatures = value;
                 OnPropertyChanged("SpecialFeatures");
             }
         }
  
         public ReadOnlyObservableCollection<Bid> Bids
         {
             get { return new ReadOnlyObservableCollection<Bid>(this.bids); }
         }
  
         public int CurrentPrice
         {
             get
             {
                 int price = 0;
                 // There is at least on bid on this product
                 if (this.bids.Count > 0)
                 {
                     // Get the amount of the last bid
                     Bid lastBid = this.bids[this.bids.Count - 1];
                     price = lastBid.Amount;
                 }
                 // No bids on this product yet
                 else
                 {
                     price = this.startPrice;
                 }
                 return price;
             }
         }
         #endregion
  
         public AuctionItem(string description, ProductCategory category, int startPrice, DateTime startDate, User owner, SpecialFeatures specialFeatures)
         {
             this.description = description;
             this.category = category;
             this.startPrice = startPrice;
             this.startDate = startDate;
             this.owner = owner;
             this.specialFeatures = specialFeatures;
             this.bids = new ObservableCollection<Bid>();
         }
  
         // Exposing Bids as a ReadOnlyObservableCollection and adding an AddBid method so that CurrentPrice 
         // is updated when a new Bid is added
         public void AddBid(Bid bid)
         {
             this.bids.Add(bid);
             OnPropertyChanged("CurrentPrice");
         }
  
         protected void OnPropertyChanged(string name)
         {
             if (PropertyChanged != null)
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(name));
             }
         }
     }
  
     public enum ProductCategory
     {
         Books,
         Computers,
         DVDs,
         Electronics,
         Home,
         Sports,
     }
  
     public enum SpecialFeatures
     {
         None,
         Color,
         Highlight
     }
  

image

     public class User
     {
         private string name;
         private int rating;
         private DateTime memberSince;
  
         #region Property Getters and Setters
         public string Name
         {
             get { return this.name; }
         }
  
         public int Rating
         {
             get { return this.rating; }
             set { this.rating = value; }
         }
  
         public DateTime MemberSince
         {
             get { return this.memberSince; }
         }
         #endregion
  
         public User(string name, int rating, DateTime memberSince)
         {
             this.name = name;
             this.rating = rating;
             this.memberSince = memberSince;
         }
     }

These 3 classes above define our object model.

Notice we have added private members. The “User” object is to

     public partial class App : Application
     {        
         private User currentUser;
         private ObservableCollection<AuctionItem> auctionItems = new ObservableCollection<AuctionItem>();
  

Notice, however, that ObservableCollection is underlined with red. This means that we are missing a reference.

 image

How would you figure out which assembly this belongs to?  My answer is to just right mouse click.

image

“AuctionItem” is the next custom class that we need to add to our project. We will also embed some enumerations to make the coding more readable.

ObservableCollections<>

image

Step 3 – Understanding the code

A lot of code was just added. The code does a lot. When you look at an AuctionItem, you will notice the code does a few things:

  1. Contains details about something being auctioned, such as description, price, date, owner, etc
  2. Maintains an observable collection of bids
  3. Bubbles up new PriceChanged events

 

image

 High level view of an “App” and an “AuctionItem” object

image

 

Step 4 – Add the user class

It is somewhat self explanatory what the “User” class is here for. Just think of how auctions work. The “Seller” has a “rating” and a “member since.”

Step 5 – Get to a successful compile with our data model loaded

After we load our objects into memory, we will start working on the user interface. The user interface will require work in XAML and code behind. Styles, templates and many other vital WPF/Silverlight concepts will be addressed.

------ Build started: Project: databindinglab, Configuration: Debug Any CPU ------
databindinglab -> C:\devprojects\temp\BindingConverting\bin\Debug\DataBindingLab.exe
------ Build started: Project: MyDataBinding, Configuration: Debug Any CPU ------
C:\Windows\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.DataSetExtensions.dll" /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll" /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\MyDataBinding.exe /resource:obj\Debug\MyDataBinding.g.resources /resource:obj\Debug\MyDataBinding.Properties.Resources.resources /target:winexe App.xaml.cs AuctionItem.cs Bid.cs MainWindow.xaml.cs Properties\AssemblyInfo.cs Properties\Resources.Designer.cs Properties\Settings.Designer.cs User.cs C:\devprojects\temp\MyDataBinding\obj\Debug\MainWindow.g.cs C:\devprojects\temp\MyDataBinding\obj\Debug\App.g.cs
C:\devprojects\temp\MyDataBinding\App.xaml.cs(18,22): warning CS0169: The field 'MyDataBinding.App.currentUser' is never used

Compile complete -- 0 errors, 1 warnings
MyDataBinding -> C:\devprojects\temp\MyDataBinding\bin\Debug\MyDataBinding.exe
========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ==========

image

 

 

 

Step 6 – Finish implementing the “App” object

The “App” object derives from the .NET built-in type “Application.” The App object is the global high level object that represents all the auctions taking place. It is a simple data model. There are 2 main one-to-many relationships. The first one-to-many is that an App object has many AuctionItems. The next one-to-many is that an AuctionItem has many Bids. A User object is needed because an AuctionItem has  seller and a buyer needs to be represented for the bidder of the AuctionItem.

 

 

image

Here is our final object model

image

 

 

image

 

Loading the object model is left to the App object’s “LoadAuctionData()”

The code below is in App.xaml.cs. It finally shows how to add real objects for our auction. Later, we will load this data in different ways. Way may choose to load from a relational database. Or we may choose to load from a REST-based web service. Later, when we try to build a similar app in Silverlight, we’ll try to store some things in Isolated Storage.

 

         private void LoadAuctionData()
         {
             CurrentUser = new User("John", 12, new DateTime(2003, 4, 20));
  
             #region Add Products to the auction
             User userMary = new User("Mary", 10, new DateTime(2000, 5, 2));
             User userAnna = new User("Anna", 5, new DateTime(2001, 9, 13));
             User userMike = new User("Mike", 13, new DateTime(1999, 11, 23));
             User userMark = new User("Mark", 15, new DateTime(2004, 6, 3));
  
             AuctionItem camera = new AuctionItem("Digital camera - good condition", ProductCategory.Electronics, 300, new DateTime(2005, 8, 23), userAnna, SpecialFeatures.None);
             camera.AddBid(new Bid(310, userMike));
             camera.AddBid(new Bid(312, userMark));
             camera.AddBid(new Bid(314, userMike));
             camera.AddBid(new Bid(320, userMark));
  
             AuctionItem snowBoard = new AuctionItem("Snowboard and bindings", ProductCategory.Sports, 120, new DateTime(2005, 7, 12), userMike, SpecialFeatures.Highlight);
             snowBoard.AddBid(new Bid(140, userAnna));
             snowBoard.AddBid(new Bid(142, userMary));
             snowBoard.AddBid(new Bid(150, userAnna));
  
             AuctionItem insideCSharp = new AuctionItem("Inside C#, second edition", ProductCategory.Books, 10, new DateTime(2005, 5, 29), this.currentUser, SpecialFeatures.Color);
             insideCSharp.AddBid(new Bid(11, userMark));
             insideCSharp.AddBid(new Bid(13, userAnna));
             insideCSharp.AddBid(new Bid(14, userMary));
             insideCSharp.AddBid(new Bid(15, userAnna));
  
             AuctionItem laptop = new AuctionItem("Laptop - only 1 year old", ProductCategory.Computers, 500, new DateTime(2005, 8, 15), userMark, SpecialFeatures.Highlight);
             laptop.AddBid(new Bid(510, this.currentUser));
  
             AuctionItem setOfChairs = new AuctionItem("Set of 6 chairs", ProductCategory.Home, 120, new DateTime(2005, 2, 20), userMike, SpecialFeatures.Color);
  
             AuctionItem myDVDCollection = new AuctionItem("My DVD Collection", ProductCategory.DVDs, 5, new DateTime(2005, 8, 3), userMary, SpecialFeatures.Highlight);
             myDVDCollection.AddBid(new Bid(6, userMike));
             myDVDCollection.AddBid(new Bid(8, this.currentUser));
  
             AuctionItem tvDrama = new AuctionItem("TV Drama Series", ProductCategory.DVDs, 40, new DateTime(2005, 7, 28), userAnna, SpecialFeatures.None);
             tvDrama.AddBid(new Bid(42, userMike));
             tvDrama.AddBid(new Bid(45, userMark));
             tvDrama.AddBid(new Bid(50, userMike));
             tvDrama.AddBid(new Bid(51, this.currentUser));
  
             AuctionItem squashRacket = new AuctionItem("Squash racket", ProductCategory.Sports, 60, new DateTime(2005, 4, 4), userMark, SpecialFeatures.Highlight);
             squashRacket.AddBid(new Bid(62, userMike));
             squashRacket.AddBid(new Bid(65, userAnna));
  
             this.AuctionItems.Add(camera);
             this.AuctionItems.Add(snowBoard);
             this.AuctionItems.Add(insideCSharp);
             this.AuctionItems.Add(laptop);
             this.AuctionItems.Add(setOfChairs);
             this.AuctionItems.Add(myDVDCollection);
             this.AuctionItems.Add(tvDrama);
             this.AuctionItems.Add(squashRacket);
             #endregion            
         }

 

 

The user interface is basically a grid

The grid has 5 rows an 3 columns.

image

 

You do not need to paste this code in yet.

 

     <Grid>
       <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="300"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
       </Grid.RowDefinitions>
  
       <Grid.ColumnDefinitions>
         <ColumnDefinition/>
         <ColumnDefinition/>
         <ColumnDefinition/>
       </Grid.ColumnDefinitions>
     </Grid>

 

 

The grid will have 3 columns as specified in the XAML.

image

We have the makings of a rudimentary grid user interface. So far, we are just a grid with one text label. We will need to build on this in upcoming sections.

image

Code: MainWindow.xaml 

 <Window x:Class="MyDataBinding.MainWindow"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     Title="MainWindow" Height="300" Width="300">
  
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="300"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition/>
             <ColumnDefinition/>
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>
         <TextBlock Grid.Row="0" Grid.ColumnSpan="3"
                    Margin="8,20,8,8">List of items for sale:</TextBlock>
     </Grid>
 </Window>
  
  
 The list box will need some formatting. Style sheets will be the standard WPF/SL mechanism to format controls and text on a XAML control, such as a list box.
  
 <Application x:Class="MyDataBinding.App"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     Startup="AppStartup">
     <Application.Resources>
  
         <Style x:Key="titleStyle" TargetType="TextBlock">
             <Setter Property="FontWeight" Value="Bold"/>
             <Setter Property="Foreground" Value="DodgerBlue"/>
             <Setter Property="FontSize" Value="18"/>
         </Style>
  
  
     </Application.Resources>
 </Application>
  
 Style Sheets – Key Lesson
 Know how style sheets work is key. The concepts are similar to CSS style sheets. Here is where you can learn more.
  
 Here are some sites you can learn more:
 
 Video at WindowsClient
 https://windowsclient.net/learn/video.aspx?v=76368
 Christian Moser Blog
 https://www.wpftutorial.net/Styles.html
  
 Simple example of a style sheet in our project. 
 If  you open up the project at this stage, you can see the style sheets and how they are implemented. Just by looking at this project (look in App.xaml and MainWindow.xaml) and you can see how to do styles. They are very simple.

image

  

image

 The next step is to go MainWindow.xaml and add some attributes to the <Window object.
  
 Notice we are adding:
  • Title
  • SizeToContent
  • ResizeMode

image

  
 <Window x:Class="MyDataBinding.MainWindow"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     Title="List of Products"
     SizeToContent="WidthAndHeight" 
     ResizeMode="NoResize"        >
  
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="300"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition/>
             <ColumnDefinition/>
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>
         <TextBlock 
             Style="{StaticResource titleStyle}"
             Grid.Row="0" Grid.ColumnSpan="3"
                    Margin="8,20,8,8">List of items for sale:</TextBlock>
     </Grid>
 </Window>

 

 

Let’s add 3 more checkboxes to MainWindow.xaml

         <CheckBox Name="Grouping" Grid.Row="1" Grid.Column="0" 
             Checked="AddGrouping" Unchecked="RemoveGrouping" 
             Margin="8" Style="{StaticResource checkBoxStyle}">Group by category</CheckBox>
         <CheckBox Name="Filtering" Grid.Row="1" Grid.Column="1" 
           Checked="AddFiltering" Unchecked="RemoveFiltering" 
           Margin="8" Style="{StaticResource checkBoxStyle}">Show only bargains</CheckBox>
         <CheckBox Name="Sorting" Grid.Row="1" Grid.Column="3" 
             Checked="AddSorting" Unchecked="RemoveSorting" 
             Margin="8" Style="{StaticResource checkBoxStyle}">Sort by category and date</CheckBox>

Notice that we will need to add styles too. Notice there is a “checkBoxStyle.” This time we’ll add the styles directly from within MainWindow.xaml

         <Style x:Key="checkBoxStyle" TargetType="{x:Type CheckBox}">
             <Setter Property="Foreground" Value="#333333" />
             <Setter Property="FontWeight" Value="Bold"/>
         </Style>

 

image

The version below contains:

  • Column and Row definitions
  • A TextBlock and 3 Checkboxes
  • Some styles for the Checkboxes

There are some things you need to notice below. One thing to notice is that “code-behind” is required. Notice that each of the 3 CheckBoxes make a reference to the following event procedures:

  1. AddGrouping/RemoveGrouping
  2. AddFiltering/RemoveFiltering
  3. AddSorting/RemoveSorting

 

image

 Notice that MainWindow 
 public partial class MainWindow : Window
 {
     CollectionViewSource listingDataView;

     public MainWindow()...
     private void OpenAddProductWindow(object sender, RoutedEventArgs e)...
     private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)...
     private void AddGrouping(object sender, RoutedEventArgs args)...
     private void RemoveGrouping(object sender, RoutedEventArgs args)...
     private void AddSorting(object sender, RoutedEventArgs args)...
     private void RemoveSorting(object sender, RoutedEventArgs args)...

}

Here is the first draft of the main window. We are just testing to see if we can get a clean startup with our data model loaded. We need the data model in place first before the controls so that when we bind our controls to data, there’s actually data in place to bind to.

 <Window x:Class="MyDataBinding.MainWindow"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     Title="List of Products"
     SizeToContent="WidthAndHeight" 
     ResizeMode="NoResize"        >
     <Window.Resources>
         <Style x:Key="checkBoxStyle" TargetType="{x:Type CheckBox}">
             <Setter Property="Foreground" Value="#333333" />
             <Setter Property="FontWeight" Value="Bold"/>
         </Style>
     </Window.Resources>
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="300"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition/>
             <ColumnDefinition/>
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>
         <TextBlock 
             Style="{StaticResource titleStyle}"
             Grid.Row="0" Grid.ColumnSpan="3"
                    Margin="8,20,8,8">List of items for sale:</TextBlock>
  
         <CheckBox Name="Grouping" Grid.Row="1" Grid.Column="0" 
             Checked="AddGrouping" Unchecked="RemoveGrouping" 
             Margin="8" Style="{StaticResource checkBoxStyle}">Group by category</CheckBox>
         <CheckBox Name="Filtering" Grid.Row="1" Grid.Column="1" 
           Checked="AddFiltering" Unchecked="RemoveFiltering" 
           Margin="8" Style="{StaticResource checkBoxStyle}">Show only bargains</CheckBox>
         <CheckBox Name="Sorting" Grid.Row="1" Grid.Column="3" 
             Checked="AddSorting" Unchecked="RemoveSorting" 
             Margin="8" Style="{StaticResource checkBoxStyle}">Sort by category and date</CheckBox>
     </Grid>
 </Window>
  

Here is the MainWindow.xaml.cs code behind

    /// <summary>
     /// Interaction logic for MainWindow.xaml
     /// </summary>
     public partial class MainWindow : Window
     {
         public MainWindow()
         {
             InitializeComponent();
         }
         private void OpenAddProductWindow(object sender, RoutedEventArgs e)
         {
  
         }
         private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)
         {
  
         }
         private void AddGrouping(object sender, RoutedEventArgs args)
         {
  
         }
         private void RemoveGrouping(object sender, RoutedEventArgs args)
         {
  
         }
         private void AddSorting(object sender, RoutedEventArgs args)
         {
  
         }
         private void RemoveSorting(object sender, RoutedEventArgs args)
         {
  
         }
         private void AddFiltering(object sender, RoutedEventArgs args)
         {
  
         }
         private void RemoveFiltering(object sender, RoutedEventArgs args)
         {
  
         }
     }

What it looks like with 3 checkboxes and 1 textblock

image

Visual Studio provides a “Document Outline” interface. It provides and excellent glimpse into the structure of your XAML objects.

image

 Running the Project – Next Step, Add ListBox
 At this point we have succeeded at a few things:
  •  Creating and implementing a data model for AuctionItems, Bids, Users, etc
    
  •  Loading some test data into the data model
    
  •  Creating a base UI that we can leverage for more functionality
    

 

image

 

Now we will add <CollectionViewSource>.

image

The CollectionViewSource object gives us an easy way to link a window to a property.

 

image

image

AuctionItems is our main data store.

image

The next logical step is to add the listbox. Notice the <ListBox> has a “ItemsSource” property that points to “listingDataView.” As you recall, “listingDataView” is simply a pointer to the “AuctionItems” collection in the “App” object.

image

The next area we want to explore is the <ListBox.GroupStyle> section, which is how we

 

image

We have a grouping capability within our listbox.

 

 

 

image

 

You can reach me, Bruno, at bterkaly@microsoft.com