【问题标题】:Adding data to datagrid while scrolling crashes when running via .exe WPF通过 .exe WPF 运行时在滚动崩溃时将数据添加到数据网格
【发布时间】:2021-09-14 06:32:12
【问题描述】:

我有一个数据网格,当剩余的记录少于 50 条时,会触发检索后续记录的事件。我在 Task.Run 中开始下载过程。

有一种情况,当用户用鼠标将滚动条滚动到末尾时,程序开始永远下载新记录。 当我通过 Visual Studio 运行程序时,界面没有崩溃,我可以通过向上移动滚动条来中断下载。

但是当通过.exe启动程序时,界面被冻结,无法进行任何操作。

在这种情况下,总是 e.ExtentHeight - e.VerticalOffset == 27。

我尝试在每次调用下载后以某种方式设置滚动条以获得 e.ExtentHeight - e.VerticalOffset> 50。但我不知道如何为 DataGrid 执行此操作我只看到 ScrollIntoView () 但是当我使用它时我没有看到任何变化。

我做了一个测试程序,当.exe启动时出现同样的问题:

public partial class MainWindow : Window
{

    public List<Event> EventsList { get; set; }
    public MainWindow()
    {
        InitializeComponent();
        EventsList = new List<Event>();
    }

    private void EventDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        Task.Run(() =>
        {
            if (e.ExtentHeight - 50 < e.VerticalOffset)
                TakeEvents();
        });
    }
    Random r = new Random();
    int lastIndex = 0;
    private void TakeEvents()
    {
        Stopwatch s = new Stopwatch();
        s.Start();

        for (int i = 0; i < 900; i++)
        {
            EventsList.Add(new Event()
            {
                Device = 1,
                Index = lastIndex++
            });
        }
        s.Stop();
        RefreshEventsList();
    }
    private void RefreshEventsList()
    {
        Task.Run(() =>
        {
            Dispatcher.Invoke(() =>
            {
                var filtrList = EventsList.Where(x => x != null && x.Device != 0).ToList();
                EventsDataGrid.ItemsSource = filtrList;
            });
        });
    }

}
public class Event
{
    public int Index { get; set; }
    public int Device { get; set; }
}

XAML:

<Grid>
    <DataGrid x:Name="EventsDataGrid" FontSize="15" Grid.Row="1" AutoGenerateColumns="False" ItemsSource="{Binding}" IsReadOnly="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Stretch" 
                  Background="Transparent" Foreground="Black" HeadersVisibility="Column" VerticalScrollBarVisibility="Visible" BorderThickness="0" ScrollViewer.ScrollChanged="EventDataGrid_ScrollChanged" >
        <DataGrid.Columns>
            <DataGridTextColumn Width="*" Binding="{Binding Index}" Header="Index" Foreground="Black"/>
            <DataGridTextColumn Width="*" Binding="{Binding Device}" Header="Device" Foreground="Black"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

【问题讨论】:

标签: c# wpf


【解决方案1】:

不确定您的代码到底应该做什么,但您至少应该进行以下修改:

使 ScrollChanged 处理程序异步并等待任务。如果没有必要,不要运行任务:

private async void EventDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (e.ExtentHeight - 50 < e.VerticalOffset)
    {
        await Task.Run(TakeEvents);
    }
}

在后台线程中创建过滤后的List,不要为了调用Dispatcher.Invoke而运行Task:

private void TakeEvents()
{
    ...
    s.Stop();

    var filteredList = EventsList.Where(e => e.Device != 0).ToList();

    Dispatcher.Invoke(() => EventsDataGrid.ItemsSource = filteredList);
}

【讨论】:

    【解决方案2】:

    尝试以下,放置一些cmets如果不清楚请留下cmets

    public partial class MainWindow : Window
    {
    
        public List<Event> EventsList { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            EventsList = new List<Event>();
        }
    
        private void EventDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (_blockRefresh)
                return;
    
            if (e.ExtentHeight - 50 < e.VerticalOffset)
                TakeEvents();
        }
        private int _lastIndex = 0;
        private bool _blockRefresh;
    
        private void TakeEvents()
        {
            Stopwatch s = new Stopwatch();
            s.Start();
    
            for (int i = 0; i < 900; i++)
            {
                EventsList.Add(new Event()
                {
                    Device = 1,
                    Index = _lastIndex++
                });
            }
            s.Stop();
            RefreshEventsList();
        }
        private void RefreshEventsList()
        {
            _blockRefresh = true;
            _ = Dispatcher.BeginInvoke((Action)(() =>
            {
                var oldList = EventsDataGrid.ItemsSource as IList<Event>;
                var oldIndex = oldList?.Count - 1 ?? -1;
                var filterList = EventsList.Where(x => x != null && x.Device != 0).ToList();
    
                EventsDataGrid.ItemsSource = filterList;
                
                //scroll to the previous last item, to prevent the condition e.ExtentHeight - 50 < e.VerticalOffset to be true once again prevents endless loop
                //scrolling will jump if scrollbar is moved by holding the left mouse button
                if (oldIndex != -1)
                    EventsDataGrid.ScrollIntoView(oldIndex);
    
                _ = Dispatcher.BeginInvoke((Action)(() =>
                {
                    //when UI updated "unlock" the refresh routine
                    _blockRefresh = false;
                }), DispatcherPriority.ApplicationIdle);
            }), DispatcherPriority.ApplicationIdle);
        }
    }
    

    【讨论】:

    • 无法将 lambda 表达式转换为类型“DispatcherPriority”,因为它不是委托类型。我使用了 System.Windows.Threading。
    • @SilnyToJa - 更新代码使其编译为 .net 框架(在我的演示中使用 .net 5)
    • 很遗憾我无法使用 net 5.0, max 4.6.1
    • @SilnyToJa - 编辑后的代码应该可以工作,再试一次
    • 如果 (_blockRefresh) 返回,我删除;它按预期工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-21
    • 2014-06-02
    • 1970-01-01
    • 2015-02-15
    • 1970-01-01
    相关资源
    最近更新 更多