c# - WPF:DataGrid 上的水平滚动查看器在重新实例化绑定(bind)的 ObservableCollection 时捕捉到右侧

标签 c# wpf

我目前在 WPF 中遇到一个问题,当重新实例化绑定(bind)的 ObservableCollection 时,DataGrid 的水平 ScrollViewer 会捕捉到可能的滚动空间的右侧(显示 DataGrid 最右边的内容)。

即使我在调用绑定(bind)事件时触发手动将 Horizo​​ntalOffset 设置为 0 的行为,并在重新绑定(bind)列表后立即调用该事件,0 也会被忽略并且捕捉再次转到右侧。我认为这与 ScrollViewer 中的操作顺序和命令队列有关。

这似乎应该是默认行为(我不确定为什么您会希望在填充数据时滚动条默认对齐到右侧)。有人知道解决此问题的方法吗?

根据要求,我的复制项目中的代码文件。

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

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

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

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

使用“重新绑定(bind)”按钮会显示我在上面描述的行为。确保您稍微向右滚动,水平滚动条将在重新绑定(bind)时捕捉到右侧。如果滚动条完全位于左侧,水平滚动条将正确地向左对齐,正如我希望它在所有情况下都这样做。

干杯

最佳答案

这是由列自动生成功能引起的。每次更改数据时,它都会删除所有列(我猜在这个阶段滚动条位置丢失)并根据传递的数据创建新列。如果您静态定义列并使用 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>

如果您想动态生成列并且还需要记住滚动条位置,您可以使用反射从代码后面生成列。缺点是不能绑定(bind),必须手动生成。例如

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

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

关于c# - WPF:DataGrid 上的水平滚动查看器在重新实例化绑定(bind)的 ObservableCollection 时捕捉到右侧,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57090666/

相关文章:

c# - 为什么不调用此命令?

c# - parent 通知 child 职位已更改

c# - 重载、泛型和类型约束 : method resolution

c# - 检查是否成功卸载

c# - 任务结果事件完成

c# - C# WPF 中的页面、框架、导航窗口

c# - Winforms ComboBox DataBinding DisplayMember 到 SubObject 属性

c# - 强类型 ActionLink 的注意事项

c# - C#ElasticSearch NEST聚合:分组依据

具有居中和剪切功能的 WPF 图像主机