简体   繁体   English

WPF:在重新实例化绑定的ObservableCollection时,DataGrid上的水平ScrollViewer捕捉到右侧

[英]WPF: Horizontal ScrollViewer on DataGrid snapping to right side on reinstantation of the bound ObservableCollection

I am currently experiencing an issue in WPF where the horizontal ScrollViewer of a DataGrid snaps to the right side of the possible scroll space (showing the right most content of the DataGrid) when reinstantiating the bound ObservableCollection. 我目前在WPF中遇到一个问题,当重新实例化绑定的ObservableCollection时,DataGrid的水平ScrollViewer捕捉到可能的滚动空间的右侧(显示DataGrid的最右边的内容)。

Even if I trigger a behavior to manually set the HorizontalOffset to 0 when a bound event is invoked, and invoke the event immediately after rebinding the list, the 0 is ignored and the snap goes to the right side again. 即使触发绑定事件时我触发将手动将Horizo​​ntalOffset设置为0并在重新绑定列表后立即调用该事件的行为,该0也将被忽略,并且捕捉再次移至右侧。 I assume this is something to do with the order of operations and command queue inside the ScrollViewer. 我认为这与ScrollViewer中的操作顺序和命令队列有关。

This seems like something that should be default behaviour (I'm not sure why you'd ever want a scroll bar to snap to right side by default when the data is populated). 这似乎应该是默认行为(我不确定为什么您不希望在填充数据时默认情况下希望滚动条对齐到右侧)。 Does anyone know of a workaround to this issue? 有人知道该问题的解决方法吗?

As requested, code files from my replication project. 根据要求,复制项目中的代码文件。

MainWindow.xaml MainWindow.xaml

<Window x:Class="WpfScrollViewer.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfScrollViewer"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Orientation="Horizontal">
        <Button Content="Rebind" Command="{Binding RebindCommand}"/>
        <Button Content="Clear and Set" Command="{Binding ClearCommand}"/>
    </StackPanel>

    <DataGrid ItemsSource="{Binding People}" Grid.Row="1" HorizontalScrollBarVisibility="Visible" FontSize="30">

    </DataGrid>
</Grid>

Person.cs Person.cs

namespace WpfScrollViewer
{
    public class Person
    {
        public string FirstNames { get; set; }

        public string LastName { get; set; }

        public int Age { get; set; }

        public string Address { get; set; }

        public string PostCode { get; set; }

        public string PhoneNumber { get; set; }
    }
}

DelegateCommand.cs DelegateCommand.cs

using System;
using System.Windows.Input;

namespace WpfScrollViewer
{
    public class DelegateCommand : ICommand
    {
        private readonly Action _fn;
        private readonly Func<bool> _canExecute;

        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action fn, Func<bool> canExecute = null)
        {
            _fn = fn;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute();
        }

        public void Execute(object parameter)
        {
            _fn();
        }
    }
}

MainViewModel.cs MainViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

namespace WpfScrollViewer
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private readonly Random _random = new Random();

        private ObservableCollection<Person> _people;

        public ObservableCollection<Person> People
        {
            get => _people;
            set
            {
                if (_people == value)
                {
                    return;
                }

                _people = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(People)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand RebindCommand { get; }

        public ICommand ClearCommand { get; }

        public MainViewModel()
        {
            RebindCommand = new DelegateCommand(RebindPeople);
            ClearCommand = new DelegateCommand(ClearAndSetPeople);
        }

        private void RebindPeople()
        {
            People = new ObservableCollection<Person>(GetPeople());
        }

        private void ClearAndSetPeople()
        {
            var people = GetPeople();
            People.Clear();
            foreach (var person in people)
            {
                People.Add(person);
            }
        }

        private List<Person> GetPeople()
        {
            var people = new List<Person>
            {
                new Person
                {
                    FirstNames = "John",
                    LastName = "Doe",
                    Address = "17 Random Street",
                    PostCode = "RN32 2JR",
                    Age = 31,
                    PhoneNumber = "07647123456"
                },
                new Person
                {
                    FirstNames = "Jane",
                    LastName = "Doe",
                    Address = "17 Random Street",
                    PostCode = "RN32 2JR",
                    Age = 30
                },
                new Person
                {
                    FirstNames = "Jack",
                    LastName = "Freens",
                    Address = "37 Badboi Lane",
                    Age = 30
                },
                new Person
                {
                    FirstNames = "Richard",
                    LastName = "Brodget",
                    Address = "69 Meme Street",
                    Age = 31
                },
                new Person
                {
                    FirstNames = "Sam",
                    LastName = "Orfitt",
                    Address = "16 Withernsea Road",
                    Age = 29
                },
                new Person
                {
                    FirstNames = "Tom",
                    LastName = "Orfitt",
                    Address = "16 Withernsea",
                    Age = 27
                }
            };

            var rmCount = _random.Next(1, 4);
            for (var i = 0; i < rmCount; i++)
            {
                people.RemoveAt(_random.Next(people.Count));
            }

            return people;
        }
    }
}

Using the "Rebind" button shows the behaviour I've described above. 使用“重新绑定”按钮将显示我上面描述的行为。 Make sure you scroll slightly over to the right, and the horizontal scroll bar will snap to the right on rebind. 确保稍微向右滚动,水平滚动条在重新绑定时将向右对齐。 If the bar is fully over to the left, the horizontal scroll bar will correctly snap to the left, as I'd like it to do in all situations. 如果该栏完全位于左侧,则水平滚动条将正确对齐到左侧,就像我希望在所有情况下一样。

Cheers 干杯

It is caused by Columns Auto Generation feature. 这是由列自动生成功能引起的。 Everytime you change data it will drop all columns (I gues that in this stage is scrollbar location lost) and creates new columns based on passed data. 每次更改数据时,它将删除所有列(我猜这是滚动条位置丢失),并根据传递的数据创建新列。 If you define columns staticaly and disables that feature using AutoGenerateColumns="False" parameter it wont reset scrollbar position. 如果您静态定义列并使用AutoGenerateColumns="False"参数禁用该功能,则不会重置滚动条位置。

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding People}" Grid.Row="1" HorizontalScrollBarVisibility="Visible" FontSize="30">
    <DataGrid.Columns>
        <DataGridTextColumn Header="FirstNames" Binding="{Binding FirstNames}" />
        <DataGridTextColumn Header="LastName" Binding="{Binding LastName}" />
        <DataGridTextColumn Header="Age" Binding="{Binding Age}" />
        <DataGridTextColumn Header="Address" Binding="{Binding Address}" />
        <DataGridTextColumn Header="PostCode" Binding="{Binding PostCode}" />
        <DataGridTextColumn Header="PhoneNumber" Binding="{Binding PhoneNumber}" />
    </DataGrid.Columns>
</DataGrid>

If you want to generate columns dynamicaly and also need to remember scrollbar position you can generate Columns from code behind using reflection. 如果要动态生成列并且还需要记住滚动条的位置,则可以使用反射从后面的代码生成列。 Disadvantage is that you cannot bind it but must it generate manually. 缺点是您不能绑定它,但必须手动生成。 For example 例如

<DataGrid AutoGenerateColumns="False" Loaded="DataGrid_Loaded" ItemsSource="{Binding People}" Grid.Row="1" HorizontalScrollBarVisibility="Visible" FontSize="30">
</DataGrid>

and DataGrid_Loaded: 和DataGrid_Loaded:

DataGrid dg = (DataGrid)sender;
MainViewModel mvm = (MainViewModel)this.DataContext;
Type classType = typeof(Person);
PropertyInfo[] properties = classType.GetProperties();
foreach (PropertyInfo prop in properties) {
    dg.Columns.Add(new DataGridTextColumn() {
        Binding = new Binding(prop.Name),
        Header = prop.Name
    });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM