Since you're new to WPF, I'm going to go step by step on this, therefore it can get a little long and sometimes repeating, but bear with me.
Note: First of all, WPF might look like a complex, intimidating framework when you start looking into it, but it's not. It's actually really simple once you get to the realization that UI is not Data and start thinking all UI functionality in terms of DataBinding-based interactions between your UI components and your Data Model.
There's a very good series of articles by Reed Copsey, Jr. about the mindshift needed when moving from winforms to WPF. There's also a much shorter article by Rachel Lim I always point people to that gives a nice overview of the WPF mentality.
Step 1:
So, Let's use your Appliance
class as a starting point for this:
public class Appliance
{
public int Id { get; set; }
public string Name { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public int Type { get; set; }
public string Color { get; set; }
public bool Visible { get; set; }
}
Notice that I used double
instead of int
for size and position, because WPF actually uses doubles since those are device-independent units rather than pixels, and string
for Color because it simplifies the example (we can actually use "Red", "Green", and so on).
Step 2:
ListCollectionfor each itemItemsControlAssuming we just created our project in Visual Studio using File -> New Project -> WPF Application
, this is the default XAML for MainWindow
:
<Window x:Class="FloorPlan.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid></Grid>
</Window>
We'll get rid of the <Grid></Grid>
part since we don't need that, and replace it by our ItemsControl
:
<ItemsControl ItemsSource="{Binding}"/>
Notice that I'm Binding the ItemsSource property. WPF is going to resolve this simple Binding to whatever the DataContext of the ItemsControl is, therefore we will assign this in code behind (by now):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Let's assign our DataContext here:
this.DataContext = new List<Appliance>
{
new Appliance() {Top = 20, Left = 40, Width = 30, Height = 30, Color = "Red"},
new Appliance() {Top = 100, Left = 20, Width = 80, Height = 20, Color = "Blue"},
new Appliance() {Top = 60, Left = 40, Width = 50, Height = 30, Color = "Green"}
};
}
}
Notice that we're actually setting the Window's DataContext, rather than the ItemsControl
, but thanks to WPF's Property Value Inheritance, the value of this property (and many others) is propagated down the Visual Tree.
Let's run our project to see what we have so far:
Not much is it? Don't worry, we're just getting started. The important thing to note here is that there's 3 "things" in our UI, which correspond to the 3 items in the List<Appliance>
in our DataContext.
Step 3:
By default, the ItemsControl will stack elements vertically, which isn't what we want. Instead, we're going to change the ItemsPanel from the default StackPanel
to a Canvas
, like this:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And then, in order to have each UI element properly positioned and sized, we're going to Style the Item Containers so that they will take the values from the Top
, Left
, Width
, and Height
properties from the Appliance
class:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Width" Value="{Binding Width}"/>
<Setter Property="Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Notice that we're binding Canvas.Left
and Canvas.Top
as opposed to just Left
and Top
, because the WPF UI elements don't have a Top
and Left
properties themselves, but rather these are Attached Properties defined in the Canvas
class.
Our project now looks like this:
Step 4:
Now we got sizing and positioning right, but we still get this ugly text instead of a proper visual for our Appliances. This is where Data Templating comes into play:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Width" Value="{Binding Width}"/>
<Setter Property="Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
By setting the ItemsControl.ItemTemplate
we define how we want each item in the List<Appliance>
to be visually represented.
Notice that I'm binding the Border.Background
property to the Color
property, which is defined in the Appliance
class. This is possible because WPF sets the DataContext
of each visual item in the ItemsControl
to it's corresponding Data Item in the List<Appliance>
.
This is our resulting UI:
Starting to look better, right? Notice we didn't even write a single line of code so far, this has all been done with just 20 lines of declarative XAML and DataBinding.
Next Steps:
So, now we have the basics in place, and we have achieved the goal of keeping the Appliance
class completely decoupled from the UI, but still there are a lot of features that we may want to include in our app:
- Selecting items by clicking on them: this can be achieved by changing the
ItemsControl
to a ListBox
(while leaving its properties intact) just by using Ctrl+H. Since ListBox
actually derives from ItemsControl
, we can use the XAML we wrote so far.
- Enable click-and-drag: This can be achieved by putting a
Thumb
control inside the DataTemplate
and handling its DragDelta
event.
- Enable two-way DataBinding: This will allow you to modify the properties in the
Appliance
class and have WPF automatically reflect the changes in the UI.
- Editing items' properties: We might want to create an edition panel where we put TextBoxes and other controls that will allow us to modify the properties of each appliance.
- Add support for multiple types of objects: For our app to be complete, it will need different object types and their respective visual representations.
For examples of how to implement these features, see my Lines Editor and Nodes Editor samples.
I think this will be a good starting point for your app, and a good introduction to WPF as well.
It's important that you take some time to read all the linked material to get a solid understanding of the concepts and underlying APIs and features that we're using here.
Let me know if you need further help or post a new question if you need to implement any of the extra features.
I think I don't even need to mention the fact that implementing all this in winforms would be significantly more tedious, with lots of code behind and much more work and an inferior result.
ListBox
and a Model class similar to yourAppliance
class, all while keeping UI code minimal, and your application logic and data layer properly separate from the UI. Also, using WPF you can achieve resolution independence, implement zooming, and use vector graphics which are much better than bitmaps. – Federico Berasategui