Search code examples
c#wpfmvvmdatatemplateitemscontrol

ItemsControl having multiple DataTemplate in WPF


I would like to draw in canvas different shapes. How can I make object from ArrowsItems ObservableCollection and CircleItems ObservableCollecton visible in canvas? I am also creating Shapes ObservableCollection including every Circle and Arrows Items. I think that propably the reason is in the data binding but don't know where.

The goal is possibility to generate and then draw programmatically circles and arrows.

  <Button Grid.Row="1" MaxWidth="1000" Command="{Binding CreateEllipse}">Utwórz</Button>
        <Viewbox Grid.Row="2" Margin="0 20 0 0" Stretch="Uniform" StretchDirection="Both" VerticalAlignment="Stretch">
          <ItemsControl Name="Shape" ItemsSource="{Binding Shapes}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                <Canvas Width="2000" Height="1200" />
              </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
              <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X, Mode=TwoWay}"/><Setter Property="Canvas.Top" Value="{Binding Y, Mode=TwoWay}"/>
              </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.Resources>
              <DataTemplate DataType="{x:Type core:CircleItem}">
                <Viewbox Width="{Binding Width}" Height="{Binding Height}">
                  <!--MouseMove="Viewbox_MouseMove"-->
                  <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseMove">
                      <i:InvokeCommandAction Command="{Binding DataContext.MyCommand, ElementName=Shape}" CommandParameter="{Binding}" />
                    </i:EventTrigger>
                  </i:Interaction.Triggers>

                  <i:Interaction.Behaviors>
                    <local:DragBehavior/>
                  </i:Interaction.Behaviors>
                  <Grid>
                    <Grid.RenderTransform>
                      <TranslateTransform X="{Binding TransformX}" Y="{Binding TransformY}" />
                    </Grid.RenderTransform>
                    <Ellipse Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Color}" />
                    <TextBlock HorizontalAlignment="Center" Text="{Binding Text}" TextAlignment="Center" VerticalAlignment="Center" />
                  </Grid>
                </Viewbox>
              </DataTemplate>
              <DataTemplate DataType="{x:Type core:ArrowItem}">
                <Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="{Binding Color}" StrokeThickness="{Binding StrokeThickness}" />
              </DataTemplate>
            </ItemsControl.Resources>
          </ItemsControl>
        </Viewbox>

Also in my ViewModel:

public ObservableCollection<CircleItem> CircleItems { get; set; }
public ObservableCollection<ArrowItem> ArrowItems { get; set; }
public CompositeCollection Shapes { get; set; }

And after adding some objects of CircleItem class to CircleItems and ArrowItem to ArrowItems:

    CompositeCollection coll = new CompositeCollection();
    coll.Add(new CollectionContainer() { Collection = CircleItems });
    coll.Add(new CollectionContainer() { Collection = ArrowItems });
    Shapes = coll;

Solution

  • Make sure you initialize the Shapes property before the view model is assigned to the DataContext. The collection properties should all be readonly, otherwise you would have to fire a property change notification from their setters.

    public class ViewModel
    {
        public ObservableCollection<CircleItem> CircleItems { get; }
            = new ObservableCollection<CircleItem>();
    
        public ObservableCollection<ArrowItem> ArrowItems { get; }
            = new ObservableCollection<ArrowItem>();
    
        public CompositeCollection Shapes { get; }
            = new CompositeCollection();
    
        public ViewModel()
        {
            Shapes.Add(new CollectionContainer { Collection = CircleItems });
            Shapes.Add(new CollectionContainer { Collection = ArrowItems });
        }
    }