0

I did small custom ItemsControl to show some list as row of items. And it works almost perfectly.

<ItemsControl.ItemTemplate>
    <DataTemplate>
       <WrapPanel Orientation="Horizontal">
           <TextBlock x:Name="delimiter" Text=";" Margin="0 0 5 0"/>
           <TextBlock Text="{Binding LinkId}" />
       </WrapPanel>
       <DataTemplate.Triggers>
          <DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
              <Setter Property="Visibility" TargetName="delimiter" Value="Collapsed"/>
          </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</ItemsControl.ItemTemplate>

My problem is in delimiter: when ItemsConteol is wrapping to next row then delimiter char goes start of next row.

I understand where is the problem, but I don't know how to sove it.

Result

Thanks in advice.

Sergey Alikin
  • 159
  • 10
  • You can actually use AlternationCount-based solution from [here](https://stackoverflow.com/a/34138980/276994) (but you need to target last item, not first one) – Vlad Mar 31 '19 at 15:12
  • @Vlad, thanks. Unfortunately, this method laso is not applyable for me: no delimiter between first and second elements. But I decide to avoid Triggers at all, in my task delimiter after last element is not a critical issue. – Sergey Alikin Mar 31 '19 at 18:12

3 Answers3

1

Your delimiter goes in front in the next line because it is in front of the LinkId TextBox and both of them are bound together inside a WrapPanel. The whole WrapPanel flows to the next line.

The parent panel above the WrapPanels does not know and does not care about what is inside them.

Example showing the approximate Layout

By the way, I don't know if it was your intention, but in your code as it is written you generate a different WrapPanel for every item in your collection. As it stands, you have 20 WrapPanels in your testlist.

If, as you say, delimiter after the last is not a problem, this is a good enough solution:

    <ItemsControl
        ItemsSource="{Binding YourItems}"
        >
        <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel
                Orientation="Horizontal"
                ></WrapPanel>
        </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel
                    Orientation="Horizontal"
                    >
                    <TextBlock
                        Text="{Binding LinkId}"
                        ></TextBlock>
                    <TextBlock
                        Text="; "
                        ></TextBlock>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

If this is only for data displaying purposes, consider that those texboxes in line could very much be a single String inside of a TextBox. Maybe you could use a single formatted String and make the relevant parts into Links. This would move the string-delimiter part of the action to the ViewModel and would simplify a lot your XAML code.

1

I have a simple solution with DataTemplateSelector. It goes like this:

You define a selector, which distinguishes the last item.

class LastElementSelector : DataTemplateSelector
{
    public DataTemplate NormalTemplate { get; set; }
    public DataTemplate LastTemplate { get; set; }
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return IsLast(container) ? LastTemplate : NormalTemplate;
    }

    bool IsLast(DependencyObject container)
    {
        var itemsControl = FindParentOrSelf<ItemsControl>(container);
        var idx = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
        var count = itemsControl.Items.Count;
        return idx == count - 1;
    }

    T FindParentOrSelf<T>(DependencyObject from) where T : DependencyObject
    {
        for (var curr = from; curr != null; curr = VisualTreeHelper.GetParent(curr))
            if (curr is T t)
                return t;
        return null;
    }
}

Having this, you can use different templates for normal and last items:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplateSelector>
        <local:LastElementSelector>
            <local:LastElementSelector.NormalTemplate>
                <DataTemplate>
                    <TextBlock>
                        <Run Text="{Binding Mode=OneWay}"/><!-- no space between runs
                        --><Run Text="; " Foreground="Red" FontWeight="Bold"/>
                    </TextBlock>
                </DataTemplate>
            </local:LastElementSelector.NormalTemplate>
            <local:LastElementSelector.LastTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </local:LastElementSelector.LastTemplate>
        </local:LastElementSelector>
    </ItemsControl.ItemTemplateSelector>
</ItemsControl>

Result:

test run

Vlad
  • 35,022
  • 6
  • 77
  • 199
0

You could try put the delimiter after the text and hide the last Run element by settings its Text property to an empty string:

<ItemsControl AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <Run Text="{Binding Path=., Mode=OneWay}" /><Run x:Name="delimiter" Text=";    "/>
            </TextBlock>
            <DataTemplate.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Text" TargetName="delimiter" Value=""/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This should keep the delimiter and text on the same line.

mm8
  • 163,881
  • 10
  • 57
  • 88