I have an observable collection of line segments specified by its boundary points. How can I bind it to draw the lines on a canvas?
I have seen the solution for shapes using only one point to define the position. But for applying this approach to lines it need awkward precomputations on coordinates to get the position of outer rectangle and make line coordinates relative to it. Is there a way to avoid it?
Here is an example how you could do it:
The line is defined as follows:
public class Line
{
public Point From { get; set; }
public Point To { get; set; }
}
MainWindow.xaml:
<Window x:Class="WpfApplication20.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding From.X}" Y1="{Binding From.Y}"
X2="{Binding To.X}" Y2="{Binding To.Y}"
Stroke="DarkGray" StrokeThickness="3"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<Line> Lines { get; private set; }
public MainWindow()
{
Lines = new ObservableCollection<Line>
{
new Line { From = new Point(100, 20), To = new Point(180, 180) },
new Line { From = new Point(180, 180), To = new Point(20, 180) },
new Line { From = new Point(20, 180), To = new Point(100, 20) },
new Line { From = new Point(20, 50), To = new Point(180, 150) }
};
InitializeComponent();
DataContext = this;
}
}
In the above example, the lines are static, i.e. if you update the From
and To
positions, the UI will not update. If you want the UI to update, you must implement INotifyPropertyChanged
for the Line
class.
This sample shows a window that looks like this:
As an aside, what I often do to avoid the ItemsControl is to use PolyLine and simply bind a collection of points. In the XAML:
<Polyline Points="{Binding Points}" Stroke="White" StrokeThickness=".1" />
Your code would simply be a PointCollection:
namespace YourNameSpace.ViewModels
{
class MainViewModel : ViewModelBase
{
#region Parameters
private PointCollection points = new PointCollection();
public PointCollection Points
{
get { return points; }
set
{
points = value;
NotifyPropertyChanged("Points");
}
}
public MainViewModel()
{
PointCollection polygonPoints = new PointCollection
{
new Point(10, 50),
new Point(100, 50),
new Point(50, 30),
new Point(75, 100),
new Point(75, 10)
};
this.Points = polygonPoints;
}
}}
I find the ItemsControl will not work in some instances. Perhaps a defect in the WPF or something I don't understand. It is certainly more complicated than simple binds which can achieve the same results.
One thing to keep in mind is that you can't just add points to the already bound control and perform a NotifyPropertyChanged and expect it to work. You have to actually set an entirely new PointCollection. You can copy the old PointCollection to the new one via a constructor so it isn't much of a hassle.