我有一个数据网格,用于显示从数据服务器读取的日志信息。
由于日志数量很大,我不想在页面初始化时一次加载数据。所以下面是我的设计:
在初始化中加载前 20 行。当用户拖动滚动条时,我将计算滑块移动的距离,获取日志的索引,然后从服务器检索新日志。将清除旧日志,以确保数据网格的内存消耗不大。
但是在实施过程中,我遇到了以下问题
1。如何设置滑块的大小?如果我只向数据网格添加 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>
吹是我的视图模型:
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();
}
我认为没有必要像这样进入WPF的具体细节。DataGrid 支持虚拟化,因此除非您做了一些事情来破坏它,否则它应该只获取屏幕上实际可见的项目。也就是说,我从未能够在不锁定 GUI 线程的情况下让 DataGrid 正确异步获取项目,因此您可能必须在视图模型中自己处理这个问题。
下面是一个在我的 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 都不支持数据虚拟化,因此我认为您最终将不得不执行类似操作才能获得您所追求的确切行为。