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.
If you click the “Add Product” button, you get this interface.
A lot of great stuff to learn
Look for “download sample” to get source code.
Step 1 – Create a new WPF Project
The project new dialog box looks like this. Notice we are creating a “WPF Application”
So you’ll end up with a nice project as follows:
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.
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.”
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.
Step 3 – Add 3 class modules
Right mouse click on the project and “Add/Class”
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;
}
}
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
}
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.
How would you figure out which assembly this belongs to? My answer is to just right mouse click.
“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<>
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:
- Contains details about something being auctioned, such as description, price, date, owner, etc
- Maintains an observable collection of bids
- Bubbles up new PriceChanged events
High level view of an “App” and an “AuctionItem” object
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 ==========
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.
Here is our final object model
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.
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.
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.
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:
|
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.
The next step is to go MainWindow.xaml and add some attributes to the <Window object.
Notice we are adding:
- Title
- SizeToContent
- ResizeMode
<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>
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:
- AddGrouping/RemoveGrouping
- AddFiltering/RemoveFiltering
- AddSorting/RemoveSorting
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
Visual Studio provides a “Document Outline” interface. It provides and excellent glimpse into the structure of your XAML objects.
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
Now we will add <CollectionViewSource>.
The CollectionViewSource object gives us an easy way to link a window to a property.
AuctionItems is our main data store.
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.
The next area we want to explore is the <ListBox.GroupStyle> section, which is how we
We have a grouping capability within our listbox.
You can reach me, Bruno, at bterkaly@microsoft.com
Comments
- Anonymous
July 31, 2013
where is the another window's tuitor?