ListView jumps or does not shrink

708 views Asked by At

First had the problem with collapse of Expander not giving back space.
Based on the borders it appears to be the ListView is not shrinking rather than the Expander not shrinking.
The following code fixed the shrinking problem.
From ListBox control does not shrink
This loses virtualization but I am OK with that as this is as much data as I need to display.

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

But then a new problem.
If you open up any two levels of Expander and try to click on the check box it will often jump around and some times jump so much that the click misses the check box.
Scroll all the way to the bottom so the check box is not at the bottom (there are some Expanders below it).
Then click and it will jump and most likely even miss the item.
I can take several attempts to check or uncheck the last row.
Pretty sure this is because of the click being processed twice and things moving between.

So how to fix both problems?
1. Collapse shrink / give back space?
2. Check box not jumping?

Tried refresh via dispatcher, UpdateLayout(), InvalidateVisual(), and Height = Double.NaN.
The StackPanel is not the problem as I can remove it and use just Exp1 and still have the problem.
Had a similar problem before on double click with SelectedItem that I was able to fix with eating the second click but that fix does not work here. MissClick
Tried TreeView but I am not displaying the same information at each level so it does not work.
But I am open to another approach.
Two level hierarchy and just need check boxes on the second level.

Sorry for a lot of code but this is a complete program to reproduce the problem.
Exp1 is identical to Exp2 and not really necessary to reproduce the problem but that reflects the real program in case someone suggests an alternate solution.

<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; }
    }
}

I think there are two bugs here.
Control not shrinking and a click getting processed twice.
I get shrink would add overhead but still why not an option.

1

There are 1 answers

4
Chris On BEST ANSWER

It took a good bit of playing with your example, but I eventually realised what the root cause of the behaviour was, and what to go looking for.

I think you're experiencing an issue caused by your CheckBox gaining focus when clicked, and raising a RequestBringIntoView event, which will essentially cause the ScrollViewer to kick the control containing your CheckBox into the "correct" place. The mechanics are explained in this answer: Stop WPF ScrollViewer automatically scrolling to perceived content.

If that scroll happens whilst you're clicking on a CheckBox, it will move out from under your cursor before you can unclick the mouse (you can demonstrate that by holding the mouse down when the problem occurs, and subsequently moving your mouse over the CheckBox in question, and releasing it).

You could create a simple event handler for the RequestBringIntoView event and use it with the CheckBox within your DataTemplate, and suppress the event using the Handled property, so it isn't propagated any further (the below worked for me).

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;
}

Your CheckBox would now no longer correctly bring itself into view if it was slightly off the screen, but it wouldn't jump unexpectedly, and you could customise the handler further if required.