Search code examples
c#wpfitemscontrol

Positioning Items in ItemsControl


I have an ItemsControl bound to data like this:

<ItemsControl Name="MainPanel" >
    <ItemsControl.ItemTemplate>
        <DataTemplate>
           <TextBlock Text="{Binding Definition}" TextWrapping="Wrap"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

In my data model I have a y coordinate for each item, to position the item vertically on the screen and each item is of variable height (because of TextWrapping).

I need each item to be positioned on the y coordinate unless it overlaps with the previous item, in which case it is placed below the previous item.

I thought to use the Margin property to do this but it is actually not that straight forward...

Any ideas on how to proceed?


Solution

  • I think change TextBlock.Margin.Top is a easy way to do this.

        <ItemsControl ItemsSource="your model collection"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <!-- Need ItemsControl for calculation. -->
                    <TextBlock Text="{Binding Text}" TextWrapping="Wrap"
                               Tag="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}}">
                        <TextBlock.Margin>
                            <MultiBinding Converter="{StaticResource calcOffsetY}">
                                <Binding RelativeSource="{RelativeSource Self}"/>
                                <Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
                            </MultiBinding>
                        </TextBlock.Margin>
                    </TextBlock>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    

    And the converter.

    public class CalcOffsetY : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var tbk = (TextBlock)values[0];
            var offsetY = tbk.TranslatePoint(tbk.RenderTransformOrigin, (UIElement)tbk.Tag).Y -
                tbk.Margin.Top;
            var y = ((YourModelType)tbk.DataContext).Y;
    
            tbk.SetCurrentValue(TextBlock.MarginProperty, new Thickness(tbk.Margin.Left, y > offsetY ? y - offsetY : 0,
                tbk.Margin.Right, tbk.Margin.Bottom));
            tbk.UpdateLayout(); // Update layout immediately, so next item will get correct result.
    
            return Binding.DoNothing; // Already nothing to do.
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }