I have a longListSelector that create several canvas dynamically and I want to draw in each canvas by using data from my ObservableCollection Games. Here is my base code of the main page:
<Grid x:Name="ContentPanel">
<phone:LongListSelector Name="myLLS" ItemSource="{Binding GamesVM}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<Canvas /> <!-- Here I want to draw -->
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
public class GameVM : INotifyPropertyChanged {
private string _title;
public string Title {
get { return this._title; }
set {
if (this._title!= value) {
this._title= value;
this.OnPropertyChanged("Title");
}
}
}
public void Draw() {
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myCanvas.Children.Add(stone);
}
}
I would like to execute my Draw method when my GamesVM collection is generated but I haven't access to the corresponding canvas at this time. Putting my Draw method in code behind doesn't help because I have no event to handle where I could get both data binding object and the canvas newly generated (except if I miss something...). So I have no "myCanvas" instance in my Draw method.
I have some ideas to do that but nothing work well.
Option 1
I can put my UIElement (Ellipse, Line, etc) in an ObservableCollection which is binded in an ItemsControl like this :
<ItemsControl ItemsSource="{Binding myUIElements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public void Draw() {
myUIElements = new ObservableCollection<UIElement>();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUIElements.Add(stone);
}
It works but when I leave the page and come back, I get an Element is already the child of another element exception. If I use VisualTreeHelper to find my ItemsControl and call Items.Clear() on it, I get an exception too beacuse Items is read-only.
Option 2
I can use a ContentControl instead of ItemsControl and create the canvas in my Draw method:
<ContentControl Content="{Binding myUICanvas"/>
public void Draw() {
myUICanvas = new Canvas();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUICanvas.Children.Add(stone);
}
It works too but when I leave the page and come back, I get a Value does not fall within the expected range exception. I understand that I can't bind UIElement because I can't clear them when the Framework try to set them again. What is the trick to say "Please, do not add the same element twice" ?
Option 3
I can try to draw directly in XAML and bind a ViewModel object instead of UIElement object.
<ItemsControl ItemsSource="{Binding myDatas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Diameter}" Fill="Black" ...>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It could work in WPF but in my Windows Phone 8 app, I have no ItemContainerStyle property to set Canvas.Left and Canvas.Right. Beside I would have to use a CompositeCollection to deal with several kind of shapes but DataType is not recognized by Visual Studio. Moreover, even if it works with Line UIElements, the render is slower than c# approach.
So, what is the best option and how to deal with my exceptions ?