JuP
JuP

Reputation: 535

Line separator in DataTemplate for ListView

I have ItemsControl with bindable itemsource and custom datetemplate for each item. Items are separated by line. But last item has separator too, and that's my problem, how to not render line for last item. I've found this solution, but it's working in WPF:

How can a separator be added between items in an ItemsControl

EDIT: Here is my template:

<ItemsControl Grid.Row="1"  ItemsSource="{x:Bind ViewModel.AvailableStatuses}" x:Name="Statuses">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Vertical" Padding="60,0,60,12"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid Background="Transparent">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <i:Interaction.Behaviors>
                                    <core:EventTriggerBehavior EventName="Tapped">
                                        <core:InvokeCommandAction Command="{Binding ElementName=ContentGrid, Path=DataContext.ChangeStatusCommand}" CommandParameter="{Binding}"/>
                                    </core:EventTriggerBehavior>
                                </i:Interaction.Behaviors>
                                <Rectangle StrokeThickness="0.4" Height="0.4" x:Name="Separator" 
                                   VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Stroke="#D1D3D4" />
                                <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Stretch">
                                    <Image Source="{Binding Converter={StaticResource SelectContactStatusConverter}}" Margin="0,8,12,8"/>
                                    <TextBlock Text="{Binding Converter={StaticResource EnumContactStatusToTextConverter}}" FontSize="20" VerticalAlignment="Center" Foreground="Black"/>
                                </StackPanel>
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>

Upvotes: 3

Views: 5201

Answers (4)

guest
guest

Reputation: 125

<ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            </Style>
        </ListView.ItemContainerStyle>

Upvotes: -3

JuP
JuP

Reputation: 535

I solved my problem this way:

private void Statuses_OnLoaded(object sender, RoutedEventArgs e)
    {
        var s = (ItemsControl) sender;
        var container = s.ContainerFromIndex(0);
        var element = container.FindChildren<Rectangle>("Separator");
        element.Visibility = Visibility.Collapsed;
    }

What do you think?

Upvotes: 1

Decade Moon
Decade Moon

Reputation: 34286

There's probably a few different ways of doing this, here's my take.

Style your ListView's item containers with the border brush and thickness you want to have, and subscribe to the ContainerContentChanging event:

<ListView ContainerContentChanging="ListView_ContainerContentChanging">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="0,1,0,0"/>
        </Style>
    </ListView.ItemContainerStyle>

    <x:String>1</x:String>
    <x:String>2</x:String>
    <x:String>3</x:String>
    <x:String>4</x:String>
</ListView>

In your code behind:

private void ListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
    if (args.InRecycleQueue)
    {
        // Item is being recycled, make sure first item has no border
        if (args.ItemIndex == 0)
        {
            var first = (ListViewItem)sender.ContainerFromIndex(0);
            if (first != null)
            {
                first.BorderThickness = new Thickness(0);
            }
        }
    }
    else if (args.ItemIndex == 0)
    {
        // A new first item
        ((ListViewItem)args.ItemContainer).BorderThickness = new Thickness(0);

        var second = (ListViewItem)sender.ContainerFromIndex(1);
        if (second != null)
        {
            second.ClearValue(BorderThicknessProperty);
        }
    }
    else
    {
        // A new internal item
        ((ListViewItem)args.ItemContainer).ClearValue(BorderThicknessProperty);
    }
}

I decided on this approach for a few reasons:

  • It makes sense to apply the border style to the ListViewItem instead of within the ItemTemplate because it will be the same for all items in the list regardless of which item template is used for a particular item. Also, it will ensure that the line touches the very left and right edge of the ListView.
  • The visual appearance of the separators can be adjusted in XAML. The code-behind can be put into a behavior (or subclass the ListView) if you want to reuse it.
  • It will maintain the correct appearance even when items are added/removed from the list, or when items are reordered.
  • It works correctly with UI virtualization.
  • It is independent of the data bound to the list view.

Screenshot


EDIT

It looks like you meant for an ItemsControl, not a ListView. If that's the case, then you'd have to do something along Depechie's answer because ItemsControl doesn't have the ContainerContentChanging event (subclass ItemsControl and override PrepareContainerForItemOverride instead), but this likely won't work for dynamic lists with items added and removed at runtime. You'd have to experiment with different solutions if this is important to you.

Upvotes: 7

Depechie
Depechie

Reputation: 6142

Maybe it's better to put the seperator on top because that will be easier to detect instead of knowing the last element. Putting it on top will result in 1 line to much for the first item, to resolve this add a binding on the visibility property of the seperator, using a converter. You pass along the item index and if the item index is 1 hide the seperator, otherwise show it.

To get the index of an item, look at an example here: http://www.bendewey.com/index.php/523/alternating-row-color-in-windows-store-listview

Upvotes: 1

Related Questions