Reputation: 5196
My application is an Universal App in Windows Phone 8.1.
I want to draw some circles on the screen but set their margin according to data in my ViewModel, I know hot to do it for one item but not for many of them.
So this is what it looks like without any binding :
<Grid>
<Canvas>
<!-- Background -->
<Rectangle Width="400" Height="400" Fill="Gray"/>
<!-- List of circles -->
<Ellipse Fill="White" Width="10" Height="10" Margin="40,300,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="80,250,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="120,240,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="160,275,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="200,275,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="240,130,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="280,150,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="320,200,0,0"/>
<Ellipse Fill="White" Width="10" Height="10" Margin="360,220,0,0"/>
</Canvas>
</Grid>
But I don't want to have multiple controls like that, I've made this in my ViewModel :
public ObservableCollection<Point> Points { get; set; }
public ObservableCollection<Thickness> Thickness { get; set; }
public MainViewModel ()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
Points = new ObservableCollection<Point>();
Points.Add(new Point(40, 250));
Points.Add(new Point(60, 300));
Points.Add(new Point(90, 150));
Thickness = new ObservableCollection<Thickness>();
foreach(Point point in Points)
{
Thickness.Add(new Thickness(point.X, point.Y, 0, 0));
}
}
}
And I want to bind for each circle (or Ellipse) the margin set in the property "Thickness". The number of circles in the canvas must be the same than the number of item in the ObservableCollection "Thickess".
I don't know how to make this clear enough so if you have some questions do not hesitate.
Upvotes: 1
Views: 487
Reputation: 21889
Margin isn't a great way to set position. In a Canvas you'd usually set the Top and Left properties, but for your case I'll recommend using a RenderTransform.
To show a list of objects based on a binding you'll need to host them in a container control such as an ItemsControl. By default the ItemsControl uses a panel which will lay out the items sequentially, but you can replace that with a Canvas and add your Ellipses as the bound data objects:
<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse StrokeThickness="{Binding Thickness}" Width="10" Height="10" Stroke="{Binding Fill}">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding Left}" Y="{Binding Top}"/>
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Canvas.Left and Canvas.Top are attached properties referring to the Ellipse's parent, so setting them in the DataTemplate won't have an effect. We could jump through some hoops to set them on the container, but setting the transform is easier.
If you prefer to set the Margin then you can bind it instead:
<Ellipse Margin="{Binding Margin}" StrokeThickness="{Binding Thickness}" Width="10" Height="10" Stroke="{Binding Fill}" />
Either way, create a class with the properties you need to bind to and bind an ObservableCollection of that type to the ItemsControl. Here's a quickie which demonstrates both positioning techniques:
class CircleObject
{
public float Top { get; set; }
public float Left { get; set; }
public double Thickness { get; set; }
public Thickness Margin {
get {
return new Thickness(Left, Top, 0, 0);
}
}
public Brush Fill { get; set; }
}
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
ObservableCollection<CircleObject> circles;
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
SolidColorBrush b = new SolidColorBrush(Colors.White);
circles = new ObservableCollection<CircleObject>();
circles.Add(new CircleObject() { Left = 40, Top = 300, Thickness = 10, Fill = b });
circles.Add(new CircleObject() { Left = 80, Top = 250, Thickness = 20, Fill = b });
circles.Add(new CircleObject() { Left = 120, Top = 240, Thickness = 30, Fill = b });
circles.Add(new CircleObject() { Left = 160, Top = 275, Thickness = 20, Fill = b });
circles.Add(new CircleObject() { Left = 200, Top = 275, Thickness = 30, Fill = b });
circles.Add(new CircleObject() { Left = 240, Top = 130, Thickness = 40, Fill = b });
circles.Add(new CircleObject() { Left = 280, Top = 150, Thickness = 50, Fill = b });
circles.Add(new CircleObject() { Left = 320, Top = 200, Thickness = 40, Fill = b });
circles.Add(new CircleObject() { Left = 360, Top = 220, Thickness = 30, Fill = b });
ic.ItemsSource = circles;
}
Upvotes: 3
Reputation: 7211
Use ItemsControl
and bind ItemsSource
to your collection.
<Grid>
<Canvas>
<!-- Background -->
<Rectangle Width="400" Height="400" Fill="Gray"/>
<!-- List of circles -->
<ItemsControl ItemsSource="{Binding Path=Thickness}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="White" Width="10" Height="10" Margin="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
Upvotes: 1