如何在MVVM中设置datagrid滚动条位置

时间:2018-08-13 03:15:55

标签: c# wpf mvvm datagrid scrollbar

我有一个数据网格,用于显示从数据服务器读取的日志信息。
由于日志数量巨大,因此我不想在页面初始化时立即加载数据。所以下面是我的设计:
在初始化中加载前20行。当用户拖动滚动条时,我将计算滑块移动的距离,获取日志索引,然后从服务器检索新日志。将清除旧日志,以确保数据网格的内存消耗不大。
但是在实施过程中,我遇到了以下问题
1.如何设置滑块的尺寸?如果我只向datagrid添加20行,则滑块非常长
2.在我拖动滑块(未完成)的过程中,datagride的内容也会自动更改。我应该如何禁用它。 下面是我的代码:

<DataGrid x:Name="LogsGrid"
          Margin="0,0,0,0"
          Height="457"
          HeadersVisibility="Column" 
          ItemsSource="{Binding LogsList}" 
          SelectedItem="{Binding SelectedRow, Mode=TwoWay}" 
          ScrollViewer.CanContentScroll="True">
<i:Interaction.Triggers>
    <local:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
        <local:CustomCommandAction Command="{Binding ScrollCommand}" />
    </local:RoutedEventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
    ...
</DataGrid.Columns>

Blew是我的视图模型:

public LogsViewModel()
{
    InitializeCommand();
    scrollTimer = new DispatcherTimer();
    scrollTimer.Interval = new TimeSpan(0,0,0,0,250);
    scrollTimer.Tick += new EventHandler(ScrollTimer_Elapsed);
}

private void ScrollTimer_Elapsed(object sender, EventArgs e)
{
    scrollTimer.Stop();
    GetNewLog(nCurrentScrollIndex);
}

private int nCurrentScrollIndex = 0;
private void OnScroll(object param)
{
    ScrollChangedEventArgs args = param as ScrollChangedEventArgs;
    if (args.VerticalOffset == 0)
    {
        return;
    }
    int nLogTotalNumber = int.Parse(LogTotalNumber);
    nCurrentScrollIndex = (int)(args.VerticalOffset/args.ExtentHeight * nLogTotalNumber);

    if (scrollTimer!= null || scrollTimer.IsEnabled)
    {
        scrollTimer.Stop();
    }
    scrollTimer.Start();
}

1 个答案:

答案 0 :(得分:1)

我认为没有必要像这样进入WPF的细节。 DataGrid支持虚拟化,因此,除非您做一些破解工作,否则它应仅获取屏幕上实际可见的项目。就是说,我一直无法让DataGrid在不锁定GUI线程的情况下异步正确地获取项目,因此您可能必须在视图模型中自行处理。

下面是一个示例,它可以轻松地在i7上管理1000万条记录,我将从模板DataGrid开始:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Text">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

这是主视图模型,它创建一个项目数组并仅在需要时才取回每个项目:

public class MainViewModel : ViewModelBase
{
    public Item[] Items { get; }

    public MainViewModel()
    {
        this.Items = Enumerable
            .Range(1, 1000)
            .Select(i => new Item { Value = i })
            .ToArray();
    }
}

public class Item : ViewModelBase
{
    public int Value { get; set; }

    private string _Text;
    public string Text
    {
        get
        {
            // if already loaded then return what we got
            if (!String.IsNullOrEmpty(this._Text))
                return this._Text;

            // otherwise load the value in a task (this will raise property changed event) 
            Task.Run(async () => {
                Debug.WriteLine($"Fetching value # {Value}");
                await Task.Delay(1000); // simulate delay in fetching the value
                this.Text = $"Item # {Value}";
            });

            // return default string for now
            return "Loading...";
        }
        set
        {
            if (this._Text != value)
            {
                this._Text = value;
                RaisePropertyChanged(() => this.Text);
            }
        }
    }

}

这当然是一个非常基本的示例,仅为了说明这一点而显示。在现实世界中,您需要设置某种缓存,并一次获取记录块。您可能还需要某种优先的预先冥想队列,以预期将来可能会获取记录。无论哪种方式,WPF都不支持数据虚拟化,所以我认为您将不得不做这样的事情来获得您想要的确切行为。