我按照此处How to: Implement Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control给出的示例为虚拟模式func(...interface{})
实现了即时加载。这很好用,但考虑到数据库的大小,我注意到在调用DataGridView
时UI线程被阻塞。为了解决这个问题,我在IDataPageRetriever
的类中实现了async-await
模式。但是,现在有很多行不显示任何值,或者我需要单击它们以使其显示值。将虚拟模式IDataPageRetriever
与DataGridView
组合在一起时,一定存在一些不直接的问题。
我认为周围有一种典型的模式,我想念一些基本的东西。
谢谢您的输入!
编辑1:添加代码
DataGridView的CellValueNeeded
async-await
缓存
private async void dgvCompound_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
var dgv = (DataGridView)sender;
try
{
e.Value = await memoryCache.RetrieveElement(e.RowIndex, dgv.Columns[e.ColumnIndex].DataPropertyName);
}
catch (OperationCanceledException)
{
}
dgv.InvalidateRow(e.RowIndex);
}
DataRetriver
public class Cache
{
private static int RowsPerPage;
public event EventHandler Initialised;
public event EventHandler CacheChanged;
// Represents one page of data.
public struct DataPage
{
public CompoundDataTable table;
public DataPage(CompoundDataTable table, int rowIndex)
{
this.table = table;
LowestIndex = MapToLowerBoundary(rowIndex);
HighestIndex = MapToUpperBoundary(rowIndex);
System.Diagnostics.Debug.Assert(LowestIndex >= 0);
System.Diagnostics.Debug.Assert(HighestIndex >= 0);
}
public int LowestIndex { get; private set; }
public int HighestIndex { get; private set; }
public static int MapToLowerBoundary(int rowIndex)
{
// Return the lowest index of a page containing the given index.
return (rowIndex / RowsPerPage) * RowsPerPage;
}
private static int MapToUpperBoundary(int rowIndex)
{
// Return the highest index of a page containing the given index.
return MapToLowerBoundary(rowIndex) + RowsPerPage - 1;
}
}
private DataPage[] cachePages;
private IDataPageRetriever dataSupply;
public Cache(IDataPageRetriever dataSupplier, int rowsPerPage)
{
dataSupply = dataSupplier;
Cache.RowsPerPage = rowsPerPage;
LoadFirstTwoPages();
}
public System.Data.SqlClient.SortOrder sortOrder
{
get { return dataSupply.sortOrder; }
set { dataSupply.sortOrder = value; }
}
public string sortByColumn
{
get { return dataSupply.sortByColumn; }
set
{
dataSupply.sortByColumn = value;
Reload();
}
}
public Dictionary<int, float> sortBySimilaritySeachResult
{
get { return dataSupply.sortBySimilaritySeachResult; }
set
{
dataSupply.sortBySimilaritySeachResult = value;
Reload();
}
}
// Sets the value of the element parameter if the value is in the cache.
private bool IfPageCached_ThenSetElement(int rowIndex, int columnIndex, ref string element)
{
if (IsRowCachedInPage(0, rowIndex))
{
if (cachePages[0].table == null || cachePages[0].table.Rows.Count == 0)
{
return true;
}
try
{
element = cachePages[0].table.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
}
catch (Exception exx)
{
throw;
}
return true;
}
else if (IsRowCachedInPage(1, rowIndex))
{
if (cachePages[1].table == null || cachePages[1].table.Rows.Count == 0)
{
return true;
}
try
{
element = cachePages[1].table.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
}
catch (Exception exx)
{
throw;
}
return true;
}
return false;
}
public async Task<string> RetrieveElement(int rowIndex, int columnIndex)
{
string element = null;
if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
{
return element;
}
else
{
return await RetrieveData_CacheIt_ThenReturnElement(rowIndex, columnIndex);
}
}
static readonly CompoundDataTable c = new CompoundDataTable();
public async Task<string> RetrieveElement(int rowIndex, string colName) => await RetrieveElement(rowIndex, c.Columns[colName].Ordinal);
private async void LoadFirstTwoPages()
{
cachePages = new DataPage[]{
new DataPage(await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(0), RowsPerPage), 0),
new DataPage(await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(RowsPerPage),RowsPerPage), RowsPerPage)
};
Initialised?.Invoke(this, EventArgs.Empty);
CacheChanged?.Invoke(this, EventArgs.Empty);
}
public async void Reload()
{
cachePages[0].table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(0), RowsPerPage);
cachePages[1].table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(RowsPerPage), RowsPerPage);
CacheChanged?.Invoke(this, EventArgs.Empty);
}
private async Task<string> RetrieveData_CacheIt_ThenReturnElement(int rowIndex, int columnIndex)
{
var IndexToUnusedPage = GetIndexToUnusedPage(rowIndex);
// Retrieve a page worth of data containing the requested value.
try
{
CompoundDataTable table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(rowIndex), RowsPerPage);
// Replace the cached page furthest from the requested cell
// with a new page containing the newly retrieved data.
cachePages[IndexToUnusedPage] = new DataPage(table, rowIndex);
return await RetrieveElement(rowIndex, columnIndex);
}
catch (OperationCanceledException)
{
cachePages[IndexToUnusedPage] = new DataPage(null, rowIndex);
throw;
}
}
// Returns the index of the cached page most distant from the given index
// and therefore least likely to be reused.
private int GetIndexToUnusedPage(int rowIndex)
{
if (rowIndex > cachePages[0].HighestIndex && rowIndex > cachePages[1].HighestIndex)
{
int offsetFromPage0 = rowIndex - cachePages[0].HighestIndex;
int offsetFromPage1 = rowIndex - cachePages[1].HighestIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
else
{
int offsetFromPage0 = cachePages[0].LowestIndex - rowIndex;
int offsetFromPage1 = cachePages[1].LowestIndex - rowIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
}
// Returns a value indicating whether the given row index is contained
// in the given DataPage.
private bool IsRowCachedInPage(int pageNumber, int rowIndex)
{
return rowIndex <= cachePages[pageNumber].HighestIndex &&
rowIndex >= cachePages[pageNumber].LowestIndex;
}
}
答案 0 :(得分:0)
就我而言,我是这样做的:我在Cache
(我称之为“数据源”)上公开了一些东西-方法LoadNextPage()
和事件PageLoaded
(可能只是一个异步方法,但我发现此拆分导致代码更简洁)和缓存的行数(在您的情况下,它将是最后一个缓存页面的HighestIndex)。
LoadNextPage()
启动数据的异步加载过程,并在加载和缓存数据时触发PageLoaded
事件。
UI类首先调用LoadNextPage()
来加载第一页,然后触发PageLoaded
,然后将网格视图的RowCount
设置为已加载的缓存行数。
此后,网格视图开始为所有单元格调用CellValueNeeded
,您可以从缓存同步填充它。当它需要用于高速缓存的最后一行的数据时,我再次调用LoadNextPage()
,然后重复该过程。
因此,一直在欺骗网格视图,使其仅缓存行,而没有其他内容。
一个要注意的是,同一行中的CellValueNeeded
可能被多次调用,因此请确保在这种情况下不要并行加载两次。
我是在NitroGit Git client for Windows中的Git日志中执行此操作的,所以有一个单向加载过程,这意味着始终缓存从1到N的页面。如果您有其他情况,例如想要从中间开始并向上滚动,甚至填充随机页面,您需要做更多的工作,但是可以利用欺骗网格视图的相同原理使缓存的行数与缓存相同,并且然后在网格视图行索引和实际数据索引之间进行映射,同时当在屏幕上的网格中达到缓存的边界时,在“一侧”填充数据。