WPF Listview 项目垂直定位 Canvas 重叠问题

WPF Listview Item Vertical Positioning with Canvas Overlap Issue

我有一个 word 插件,它有一个 WPF 自定义任务窗格。因为我有一个 Listview。当 word 文档向下滚动时,我需要动态更新每个 listview 项目的位置。但其中一些可能具有相同的 canvas.top(vertical Y) value.Then 那些项目重叠。

我不需要重叠那些,我需要因为列表视图需要一个接一个对齐。

screen sample

代码示例XAML..

<listViewTool:ListView x:Name="Results" Margin="0" BorderThickness="0" DockPanel.Dock="Top" Background="WhiteSmoke"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"                      
                  ItemsSource="{Binding Results}"
                  SelectionMode="Single">
    <listViewTool:ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="Control.Margin" Value="0"/>
            <Setter Property="Control.Padding" Value="0"/>
            <Setter Property="Control.BorderThickness" Value="0"/>
            <Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}"/>
            <Setter Property="Canvas.Top" Value="{Binding Y}"/>
            <Setter Property="Canvas.Left" Value="0"/>
            <Setter Property="Canvas.Width" Value="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListView}},Path=ActualWidth}" />
        </Style>
    </listViewTool:ListView.ItemContainerStyle>
    <listViewTool:ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas x:Name="CanvasMain" HorizontalAlignment="Stretch" Height="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListView}},Path=ActuaHeight}"
                            ClipToBounds="True" />
        </ItemsPanelTemplate>
    </listViewTool:ListView.ItemsPanel>
</listViewTool:ListView>

谢谢。

我做了一个简单的解决方案,看起来像这样:


想法是存储项目的高度并在其中一个高度更改时重新计算所有项目的位置。

项目高度是项目模板边框ActualHeight。对于每个项目:Y 是初始位置,Offset 是 calculated/used 绑定(基于当前项目之前的 Height 个项目)。

解决方案包含一些调试代码(Text 属性 和绑定)并且不是纯 MVVM(但您没有要求):

xaml:

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Canvas.Left"
                    Value="0" />
            <Setter Property="Canvas.Top"
                    Value="{Binding Offset}" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListViewItem">
                        <Border CornerRadius="10"
                                BorderThickness="1"
                                BorderBrush="Gray"
                                SizeChanged="Border_SizeChanged">
                            <Expander Header="{Binding Text}">
                                <Grid Height="50" />
                            </Expander>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListView.ItemContainerStyle>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"
                    ClipToBounds="True" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListView>

cs:

public class Item : INotifyPropertyChanged
{
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string property = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

    public double Y { get; set; }
    public double Height { get; set; }

    double _offset;
    public double Offset
    {
        get { return _offset; }
        set
        {
            _offset = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(Text));
        }
    }

    public string Text => $"Y={Y} Height={Height} Offset={Offset}";
}

public partial class MainWindow : Window
{
    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();

    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < 10; i++)
            Items.Add(new Item { Y = i * 40 });
        DataContext = this;
    }

    void Border_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var border = (Border)sender;
        var current = (Item)border.DataContext;
        current.Height = border.ActualHeight;
        // recalculate offset
        var y = Items[0].Y;
        foreach (var item in Items)
        {
            item.Offset = y > item.Y ? y : item.Y;
            y = item.Offset + item.Height;
        }
    }
}