1
votes

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 ?

1

1 Answers

0
votes

For information, I give you which one I choose.

I take option 2 and avoid the come back error by redrawing a new Canvas each time. I change my Draw definition so it return me the new Canvas.

public class GameVM : INotifyPropertyChanged {

  // Title and other properties

  private Canvas _myUICanvas;
  public Canvas myUICanvas
  {
    get {
      _myUICanvas = Draw();
      return _myUICanvas;
    }
    set {
      // this is never called
      _myUICanvas = value;
    }
  }

  public Canvas Draw() {
    Canvas newCanvas = new Canvas();
    Ellispe stone = new Ellipse();
    // [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
    newCanvas.Children.Add(stone);
    return newCanvas;
  }
}

Like this, I can run my program without error and without reloading/recreating all the GameVM instances.