【问题标题】:ListView jumps or does not shrinkListView 跳转或不收缩
【发布时间】:2025-12-31 12:45:07
【问题描述】:

第一个问题是 Expander 折叠不给回空间。
根据边框,它似乎是 ListView 没有缩小,而不是 Expander 没有缩小。
以下代码修复了缩小问题。
来自ListBox control does not shrink
这会失去虚拟化,但我可以接受,因为这是我需要显示的数据量。

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel/>
    </ItemsPanelTemplate>
</ListView.ItemsPanel>  

但是又出现了一个新问题。
如果您打开任意两个级别的 Expander 并尝试单击复选框,它会经常跳来跳去,有时会跳得太厉害以至于单击会错过复选框。
一直滚动到底部,使复选框不在底部(下方有一些扩展器)。
然后点击,它会跳起来,很可能甚至会错过该项目。
我可以尝试多次检查或取消选中最后一行。
很确定这是因为点击被处理了两次并且事情在两者之间移动。

那么如何解决这两个问题呢?
1.折叠收缩/给回空间?
2. 复选框不跳?

尝试通过调度程序、UpdateLayout()、InvalidateVisual() 和 Height = Double.NaN 进行刷新。
StackPanel 不是问题,因为我可以删除它并仅使用 Exp1 并仍然存在问题。
在双击 SelectedItem 之前有一个类似的问题,我可以通过第二次点击来解决这个问题,但是这个修复在这里不起作用。 MissClick
尝试了 TreeView,但我没有在每个级别显示相同的信息,因此它不起作用。
但我对另一种方法持开放态度。
两级层次结构,只需要第二级的复选框。

很抱歉有很多代码,但这是重现问题的完整程序。
Exp1 与 Exp2 相同,并且不是重现问题所必需的,但它反映了真实程序,以防有人提出替代解决方案。

<Window x:Class="ListViewJump.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="165"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ScrollViewer Grid.Row="0" Grid.Column="0"
                      HorizontalScrollBarVisibility="Disabled"
                      VerticalScrollBarVisibility="Visible">
            <StackPanel>
                <Expander IsExpanded="False" Header="Exp1" BorderThickness="2" BorderBrush="Green">
                    <ListView ItemsSource="{Binding Path=List1}"
                              HorizontalAlignment="Left" BorderThickness="2" BorderBrush="Orange">
                        <ListView.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel/>
                            </ItemsPanelTemplate>
                        </ListView.ItemsPanel>
                        <ListView.ItemTemplate >
                            <DataTemplate>
                                <Expander Header="{Binding Path=DispName}">
                                    <ListView ItemsSource="{Binding Path=Rows}"
                                              HorizontalAlignment="Left" BorderThickness="2" BorderBrush="Purple">
                                        <ListView.ItemTemplate >
                                            <DataTemplate>
                                                <CheckBox Width="125" IsChecked="{Binding Path=On}">
                                                    <TextBlock Text="{Binding Path=StrValue}"  TextWrapping="Wrap"/>
                                                </CheckBox>
                                            </DataTemplate>
                                        </ListView.ItemTemplate>
                                    </ListView>
                                </Expander>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </Expander>
                <Expander IsExpanded="False" Header="Exp2"  BorderThickness="2" BorderBrush="Green">
                    <ListView ItemsSource="{Binding Path=List2}"
                              HorizontalAlignment="Left" BorderThickness="2" BorderBrush="Orange">
                        <ListView.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel/>
                            </ItemsPanelTemplate>
                        </ListView.ItemsPanel>
                        <ListView.ItemTemplate >
                            <DataTemplate>
                                <Expander Header="{Binding Path=DispName}">
                                    <ListView ItemsSource="{Binding Path=Rows}"
                                              HorizontalAlignment="Left" BorderThickness="2" BorderBrush="Purple">
                                        <ListView.ItemTemplate >
                                            <DataTemplate>
                                                <CheckBox Width="125" IsChecked="{Binding Path=On}">
                                                    <TextBlock Text="{Binding Path=StrValue}"  TextWrapping="Wrap"/>
                                                </CheckBox>
                                            </DataTemplate>
                                        </ListView.ItemTemplate>
                                    </ListView>
                                </Expander>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </Expander>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</Window>

namespace ListViewJump
{
    public partial class MainWindow : Window
    {
        private List<ListItem> list1 = new List<ListItem>();
        private List<ListItem> list2 = new List<ListItem>();
        public MainWindow()
        {
            this.DataContext = this;  
            for (int i = 1; i < 10; i++)
            {
                List<ListItemRow> lr1 = new List<ListItemRow>();
                List<ListItemRow> lr2 = new List<ListItemRow>();
                for (int j = 1; j < 100; j++)
                {
                    lr1.Add(new ListItemRow("Row Row Row Row Row Row" + j.ToString()));
                    lr2.Add(new ListItemRow("Rwo Rwo Rwo Rwo Rwo Rwo" + j.ToString()));
                }
                list1.Add(new ListItem("one " + i.ToString(), lr1));
                list2.Add(new ListItem("two " + i.ToString(), lr2));
            }   
            InitializeComponent();                         
        }
        public List<ListItem> List1 { get { return list1; } }
        public List<ListItem> List2 { get { return list2; } }
    }
    public class ListItem
    {
        private string dispName;
        private List<ListItemRow> rows;
        public string DispName { get { return dispName; } } 
        public List<ListItemRow> Rows { get { return rows; } }
        public ListItem(String DispName, List<ListItemRow> Rows) { dispName = DispName; rows = Rows; }
    }
    public class ListItemRow
    {
        private string strValue;
        private bool on = false;
        public string StrValue { get { return strValue; } }
        public bool On
        {
            get { return on; }
            set { on = value; }
        }
        public ListItemRow(String StrValue) { strValue = StrValue; }
    }
}

我认为这里有两个错误。
控件不会缩小,并且单击会被处理两次。
我得到收缩会增加开销,但为什么不是一个选项。

【问题讨论】:

    标签: .net wpf listview checkbox expander


    【解决方案1】:

    您的示例花了很多时间,但我最终意识到行为的根本原因是什么,以及要寻找什么。

    我认为您遇到的问题是您的CheckBox 在单击时获得焦点并引发RequestBringIntoView 事件,这实际上会导致ScrollViewer 将包含您的CheckBox 的控件踢到“正确”的地方。这个答案解释了机制:Stop WPF ScrollViewer automatically scrolling to perceived content

    如果在您单击 CheckBox 时发生滚动,它会在您取消单击鼠标之前从您的光标下方移出(您可以通过在问题发生时按住鼠标然后移动来证明这一点将鼠标悬停在有问题的 CheckBox 上,然后释放它)。

    您可以为RequestBringIntoView 事件创建一个简单的事件处理程序,并将其与DataTemplate 中的CheckBox 一起使用,并使用Handled 属性抑制该事件,因此它不会进一步传播(以下对我有用)。

    XAML:

    <DataTemplate>
        <CheckBox Width="125" IsChecked="{Binding Path=On}" RequestBringIntoView="RequestBringIntoViewSuppressor">
            <TextBlock Text="{Binding Path=StrValue}" TextWrapping="Wrap"/>
        </CheckBox>
    </DataTemplate>
    

    C#:

    private void RequestBringIntoViewSuppressor(object sender, RequestBringIntoViewEventArgs e)
    {
        e.Handled = true;
    }
    

    如果您的CheckBox 稍微偏离屏幕,现在将无法正确显示自己,但它不会意外跳转,如果需要,您可以进一步自定义处理程序。

    【讨论】:

    • 谢谢。我还需要在 ListView 的 Expander 上使用相同的东西。部分屏幕外的项目没有进入视野比我拥有的要好得多。
    • 啊,有道理。祝你好运=D
    • 我要求从社区 wiki 中删除此内容,以便您获得荣誉。它去了维基,因为我编辑了太多次,但基本问题没有改变。
    • 你太好了,很高兴能帮到你=D