简体   繁体   中英

Set style for specific item type in ItemsControl

I have an ItemsControl , which I use to draw two different sets of shapes on a canvas. As such, I have two ItemsSource containing Edge objects and Node objects.

I have two different DataTemplates for each type. However, I need to set the canvas positioning for the nodes, but not for the edges. There are abundant examples on the internet on how to do this with a single ItemsSource , but not with multiple as in my case.

I have hacked it like this, but this throws a lot of binding errors in the output window (because only Nodes have a Position property, not Edges, and thus this 'works'). Also, I want to set the ZIndex for the Nodes and Edges separately, which is impossible in this way. Does anyone have any suggestions?

<ItemsControl>
    <ItemsControl.ItemsSource>
        <MultiBinding>
            <MultiBinding.Converter>
                <p:CompositeCollectionConverter/>
            </MultiBinding.Converter>
            <Binding Path="Graph.Nodes"/>
            <Binding Path="Graph.Edges"/>
        </MultiBinding>
    </ItemsControl.ItemsSource>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type model:Edge}">
            <Path 
                Stroke="Blue"
                Data="{Binding Path=EdgeSegments, Converter={StaticResource EdgeSegmentsConverter}}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type model:Node}">
            <Ellipse 
                Width="8" 
                Height="8" 
                Stroke="Black"
                Fill="Gray"/>
        </DataTemplate>
    </ItemsControl.Resources>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left">
                <Setter.Value>
                    <Binding Path="Position.X">
                        <Binding.Converter>
                            <p:NodePositionConverter />
                        </Binding.Converter>
                    </Binding>
                </Setter.Value>
            </Setter>
            <Setter Property="Canvas.Top">
                <Setter.Value>
                    <Binding Path="Position.Y">
                        <Binding.Converter>
                            <p:NodePositionConverter />
                        </Binding.Converter>
                    </Binding>
                </Setter.Value>
            </Setter>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

Why not use ItemContainerStyleSelector ? Add the styles to ItemsControl.Resources :

<Style TargetType="ContentPresenter" x:Key="{x:Type model:Edge}">
    <Setter Property="ZIndex">
        ...
    </Setter>
</Style>
<Style TargetType="ContentPresenter" x:Key="{x:Type model:Node}">
    <Setter Property="ZIndex">
        ...
    </Setter>
    <Setter Property="Canvas.Top">
        ...
    </Setter>
    <Setter Property="Canvas.Left">
        ...
    </Setter>
</Style>

Note the x:Key is set to the type so we can easily look up by item.GetType() in the style selector:

    public override Style SelectStyle(object item, DependencyObject container) {
        var containerElement = (FrameworkElement)container;
        var style = containerElement.TryFindResource(item.GetType()) as Style;
        if (style != null) {
            return style;
        }

        return base.SelectStyle(item, container);
    }

It's a bit weird what you are doing.. I didn't know if your really need to do it with this way...

You can't juste merge your two List into a simple List ? Like this:

List<AChild> a;
List<BChild> b;

List<Mother> ab = a.Concat(b).Cast<Mother>();

And after in your View you can use a TemplateSelector who will help you to choose which DataTemplate is appropriate for the item.

<ItemsControl ItemTemplateSelector="{StaticResource YourTemplateSelector}" ItemsSource="{Binding ab}"/>

The answer is given by Rachel in the following StackOverflow thread: https://stackoverflow.com/a/7931448/970589

So kudos to her. Short summary of the answer: Use a converter to check the type of the calling binding, and return a value according to that type.

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