简体   繁体   中英

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;

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 });
    }
}

If I understood you correctly, you want to have different templates for different datatypes. This is quite easy to get. One of the ways (probably the simplest one):

  • create data templates for every of your types you want to show
  • create some kind of data template that will select the proper template:

     <!-- Data template for arrows --> <DataTemplate x:Key="ArrowsDataTemplate" DataType="{x:Type core:ArrowItem}"> <!-- Create here your template for arrows --> </DataTemplate> <!-- create data templates for other stuff and then "selector" data template --> <DataTemplate x:Key="ContentDataTemplate"> <ContentPresenter x:Name="itemContentPresenter" ContentTemplate="{StaticResource CircleDataTemplate}" <!-- just the default one --> Content="{TemplateBinding Content}"/> <DataTemplate.Triggers> <DataTrigger Binding="{Binding MyItemTypeAsEnum}" Value="Arrow"> <Setter TargetName="itemContentPresenter" Property="ContentTemplate" Value="{StaticResource ArrowsDataTemplate"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> <!-- Now in your control (for example content control), where you show this stuff, do: --> <ContentControl ContentTemplate="{StaticResource ContentDataTemplate}"/> 

Now, I assumed that you have one base Item with property MyItemTypeAsEnum which will give you Circle for CircleItem, Arrow for ArrowItem etc. But if you don't have such property, you should be able to get boolean value from your viewmodel that will tell you if this item is Circle or not etc.

Into your main control, when you show thigs you have to set ContentTemplate to your "selector" template.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM