Introduction
First excuse my English, i'm a little out of practice.
A few days ago i was to write user control using WPF (which was 'terra incognita' for me) and i would gladly give a half of kingdom for an article i'm trying to write that time. Well...i hope it will be as useful as intended to be :)
DISCLAIMER: I'm not a WPF guru. It's just an expirience (and gathered in hurry and extreme conditions = double valued exp ) with MSDN references for whose who wants to know details and in-depth principles.
Background
I don't want to bother you with detail of real project. We'll check simplified but rather complicated for novice problem. Suppose we have some data entity:
internal class Person
{
public string Name { get; set; }
public string Company { get; set; }
public string Department { get; set; }
}
Still simple, isn't it?
Next, we need some data source. And let it look like this:
internal class PersonProvider
{
public IEnumerable<Person> Source
{
get
{
Person p = new Person() {Name = "Jeff"};
yield return p;
p = new Person() {Name = "Mike",Company="Google"};
yield return p;
p = new Person() { Name = "Alex" , Company = "Ksema",
Department="Analysis"};
yield return p;
}
}
}
So, our persons work in companies and hold position in departments. And we need a way to edit this data. One constraint: we have a predefined list of company/depatments to choose and i suggest we put it in one pretty xml file.
="1.0" ="utf-8"
<companies>
<company name="Ksema" description="Pleasant work atmosphere [Ksema co]">
<department name="Analysis"/>
<department name="Sales"/>
<department name="Marketing"/>
</company>
<company name="Google" description="For real math geeks [Google]">
<department name="Development"/>
<department name="HR"/>
</company>
</companies>
That's it, now we got all the data.
The real deed
I give all XAML description of the window, and will analyse it piece by piece later.
<Window x:Class="SampleWPF.MainWnd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Entity="clr-namespace:SampleWPF.Entity"
Title="Window1" Height="Auto" Width="Auto">
<Window.Resources>
<ResourceDictionary>
<Entity:PersonProvider x:Key="PersonProvider"/>
<XmlDataProvider x:Key="CompaniesDataProvider" Source="Data\companies.xml"/>
</ResourceDictionary>
</Window.Resources>
<DockPanel>
<ListBox x:Name="lstPersons" SelectedIndex="0"
DockPanel.Dock="Left" Width="100" DisplayMemberPath="Name"
ItemsSource="{Binding Source={StaticResource PersonProvider},Path=Source}"/>
<Grid DockPanel.Dock="Right" DataContext="{Binding Mode=Default,
Source={StaticResource CompaniesDataProvider}, XPath=/companies/company}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Name"/>
<TextBox Grid.Column="1" Grid.Row="0"
Text="{Binding Path=SelectedItem.Name,ElementName=lstPersons}"/>
<Label Grid.Column="0" Grid.Row="1" Content="Company"/>
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }"
DisplayMemberPath="@description" SelectedValuePath="@name"
SelectedValue="{Binding Path=SelectedItem.Company,
ElementName=lstPersons, Mode=Default}"
IsSynchronizedWithCurrentItem="False" x:Name="comboBox" />
<Label Grid.Column="0" Grid.Row="2" Content="Department"/>
<ComboBox Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Mode=Default,
XPath=department}" DisplayMemberPath="@name"
SelectedValue="{Binding Path=SelectedItem.Department,
ElementName=lstPersons, Mode=Default}" SelectedValuePath="@name"
IsSynchronizedWithCurrentItem="False" />
</Grid>
</DockPanel>
</Window>
All what you see in <Window> element can be placed in <UserControl> element with the same succes, so when i sad "user control" i didn't lied to you.
I assume that you have at least some framented knowledge what Windows Presentation Foundation is. And you know that there is XAML markup for interface and code behind file for it (by the way look at it in sample project )) ). Neverless if you have Visual Studio (may be also with Expression) all the boring part of work will be done by this wonderful IDEs.
We want to edit persons collection, so we need to give WPF knowledge about data which will be bound to its controls. And we do so in this block:
<Window.Resources>
<ResourceDictionary>
<Entity:PersonProvider x:Key="PersonProvider"/>
<XmlDataProvider x:Key="CompaniesDataProvider" Source="Data\companies.xml"/>
</ResourceDictionary>
</Window.Resources>
All that you need to know about it for the begining is that each child element in <ResourceDictionary> have to provide x:Key attribute which later can be used in markup to identify local static resource. For curious
Here we define XmlDataProvider which is used later for company/depatment selection and give some info on PersonProvider type.
The simplest scenario here - we just set Source attribute of XmlDataProvider to the location of our xml file. Other possibilities
We want to have a list of persons on the left side and detailed data on each selected person on right side of window(control). And here goes <DockPanel> element which allows us to place a lot of controls in it and set their docking direction (Top,Bottom,Left, Right, Fill) - you should remember that Window or UserControl allow to place only one child control in its Content, so if you want more you should use one with container capabilities (usualy intended to control layout) - StackPanel, DockPanel, Grid are commonly used but there are also a few not covered here.
To affect interaction with parent container use <ContainerType>.<ContainerProperty> attribute on child control. For example :
<ListBox x:Name="lstPersons" SelectedIndex="0"
DockPanel.Dock="Left" Width="100" DisplayMemberPath="Name"
ItemsSource="{Binding Source={StaticResource PersonProvider},
Path=Source}"/>
Here DockPanel.Dock="Left" tell the DockPanel that ListBox shoul be docked to left side. ItemsSource tells all about self, here we place magic construction {Binding Source=,Path=} and set Source to static resource PersonProvider and Path to its member we are going to use (as you remember it's the property which returns IEnumerable<Person>). DisplayMemberPath tell which member of selected item should be shown, here we want a list of names. Binding basics
Now we want to edit personal details. And for such needs i suggest to use <Grid> container - very useful thing, it allows to create table and define its rows and cols properties and place controls inside its cells.
<Grid DockPanel.Dock="Right" DataContext="{Binding Mode=Default,
Source={StaticResource CompaniesDataProvider}, XPath=/companies/company}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
Points of interest here DataContext, <Grid.ColumnDefinitions> and <Grid.RowDefinitions>. In short DataContext allows to define context for inner elements and use simplified binding syntax. More...
Defining width and height of cols and rows is easy. You can use value (point, pixels and so on are supported )
Width="120"
auto sizing
Height="Auto"
and once you can fill the rest of available place
Width="*"
So we defined the layout, now go controls themselves:
<Label Grid.Column="0" Grid.Row="0" Content="Name"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=SelectedItem.Name,
ElementName=lstPersons}"/>
Look at Grid.Column and Grid.Row, do you remember DockPanel.Dock - yes, it's the same case... well, almost the same. This kind of properties called attached. At this point we use them to instruct where to put controls in the grid. Also here we bind SelectedItem.Name of list lstPersons (you can see above that name is defined by x:Name attribute) to Text property of TextBox control.
And now most interesting - two comboboxes, one for company and one for department. And depatment combo items of course depend on selected company.
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }"
DisplayMemberPath="@description" SelectedValuePath="@name"
SelectedValue="{Binding Path=SelectedItem.Company, ElementName=lstPersons,
Mode=Default}" IsSynchronizedWithCurrentItem="False" x:Name="comboBox" />
<ComboBox Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Mode=Default,
XPath=department}" DisplayMemberPath="@name"
SelectedValue="{Binding Path=SelectedItem.Department, ElementName=lstPersons,
Mode=Default}" SelectedValuePath="@name" IsSynchronizedWithCurrentItem="False" />
You see, you see.. as i sad before, DataContext is magic, ItemSource="{Binding}" thats it, just because context is XmlDataProvider with XPath="/companies/company". Also DisplayMemberPath is in xpath form here, very useful capability of WPF, next goes SelectedValuePath - it instructs that selected value is not that shown, it's different and later we bind it to Company property of list's SelectedItem. Read this carefully, give yourself some time to understand connections between data.
Now depatments, it's list depends on company and we fill ItemsSource respectively (keep in mind that we still have the same DataContext).
That's all folks:
Suggestions
It's a good playground to start from.
1) Play with IsSynchronizedWithCurrentItem attribute
2) yield break in the begining of Source of Persons, just for fun
3) Add ToolBar to the top of the DockPanel
4) Add a couple of buttons to it (of course you know how to name it ;) )
5) Create a couple of routed commands
6) Create normal coolection for Person's (ObservableCollection for example)
7) Make it add and delete elements on created in step 5 commands
P.S.
If this article will be useful for at least one Person :) i promise to continue writing, so i wait for some feedback with corrections and applause too... Feel free to comment.