有没有办法在虚拟模式下对DataGridView进行排序?
我在这个微软示例之后以虚拟模式实现了Gridview:http://msdn.microsoft.com/de-de/library/ms171624.aspx.此外,我已经修改了示例,以便能够将数据写入数据库.这很好,虚拟模式可以大大提高速度,但我的客户需要对列进行排序.
在网上搜索了一段时间后,我找到了Link http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/25b3f7c3-95b6-4c49-802b-b5a2a62915ac,但无法让它工作.
任何人都可以指出如果有办法在虚拟模式下对列进行排序,如果是这样,我将如何做到这一点?
非常感谢提前!
解决方法:
好的,我现在解决了这个问题.使用microsoft示例(http://msdn.microsoft.com/de-de/library/ms171624.aspx)我修改了DataRetrieverLog类以在构造函数中接收一个附加值:要排序的列名(包括排序方向,例如“Name ASC”).对于任何感兴趣的人,这里是修改后的代码:
public class DataRetrieverLog : IDataPageRetriever
{
private string tableName;
private string sortColumn;
private SqlCommand command;
private DataTable table;
private SqlDataAdapter adapter;
public DataRetrieverLog(string connectionString, string tableName, string sortColumn)
{
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
command = connection.CreateCommand();
this.tableName = tableName;
this.sortColumn = sortColumn;
}
private int rowCountValue = -1;
public int RowCount
{
get
{
// Return the existing value if it has already been determined.
if (rowCountValue != -1)
{
return rowCountValue;
}
// Retrieve the row count from the database.
command.CommandText = "SELECT COUNT(*) FROM " + tableName;
rowCountValue = (int)command.ExecuteScalar();
return rowCountValue;
}
}
private DataColumnCollection columnsValue;
public DataColumnCollection Columns
{
get
{
// Return the existing value if it has already been determined.
if (columnsValue != null)
{
return columnsValue;
}
// Retrieve the column information from the database.
command.CommandText = "SELECT * FROM " + tableName;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.FillSchema(table, SchemaType.Source);
columnsValue = table.Columns;
return columnsValue;
}
}
private string commaSeparatedListOfColumnNamesValue = null;
private string CommaSeparatedListOfColumnNames
{
get
{
// Return the existing value if it has already been determined.
if (commaSeparatedListOfColumnNamesValue != null)
{
return commaSeparatedListOfColumnNamesValue;
}
// Store a list of column names for use in the
// SupplyPageOfData method.
System.Text.StringBuilder commaSeparatedColumnNames =
new System.Text.StringBuilder();
bool firstColumn = true;
foreach (DataColumn column in Columns)
{
if (!firstColumn)
{
commaSeparatedColumnNames.Append("], [");
}
else
{
commaSeparatedColumnNames.Append("[");
}
commaSeparatedColumnNames.Append(column.ColumnName);
firstColumn = false;
}
commaSeparatedColumnNames.Append("]");
commaSeparatedListOfColumnNamesValue =
commaSeparatedColumnNames.ToString();
return commaSeparatedListOfColumnNamesValue;
}
}
// Declare variables to be reused by the SupplyPageOfData method.
private string columnToSortBy;
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
// Store the name of the ID column. This column must contain unique
// values so the SQL below will work properly.
if (columnToSortBy == null)
{
columnToSortBy = this.Columns[0].ColumnName;
}
if (!this.Columns[columnToSortBy].Unique)
{
throw new InvalidOperationException(String.Format(
"Column {0} must contain unique values.", columnToSortBy));
}
// Retrieve the specified number of rows from the database, starting
// with the row specified by the lowerPageBoundary parameter.
String text = "Select Top " + rowsPerPage + " " +
CommaSeparatedListOfColumnNames + " From " + tableName +
" WHERE " + columnToSortBy + " NOT IN (SELECT TOP " +
lowerPageBoundary + " " + columnToSortBy + " From " +
tableName + " Order By " + sortColumn +
") Order By " + sortColumn;
command.CommandText = text;
adapter = new SqlDataAdapter(text, GUI.dictSettings["connectionString"]);
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.Fill(table);
return table;
}
public DataTable getDataTable()
{
return table;
}
public SqlDataAdapter getAdapter()
{
return adapter;
}
}
除此之外,我在Gridview中实现了ColumnHeaderMouseClick-Event.在那里我得到列名和排序方向,然后通过创建一个新的DataRetrieverLog实例并传递列名排序方向来重新加载所有基础表数据.接下来我只刷新Gridview:gridView.Refresh()
而已.
编辑(20.07.2015):
为了使事情更清楚,这里是在虚拟模式下使可排序的DataGridView工作的完整方法.自从我玩这个游戏以来已经有一段时间了,所以我希望我能说清楚它让它运转起来并且没有错过任何事情.要使其工作,需要三个Helper类:
IDataPageRetriever.cs:
using System.Data;
using System.Data.SqlClient;
namespace ASC.Code.Forms.Helper
{
public interface IDataPageRetriever
{
DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage);
SqlDataAdapter getAdapter();
}
}
CacheAll.cs:
using System.Data;
using System.Data.SqlClient;
namespace ASC.Code.Forms.Helper
{
public class CacheAll
{
private static int RowsPerPage;
// Represents one page of data.
public struct DataPage
{
public DataTable table;
public SqlDataAdapter adapter;
private int lowestIndexValue;
private int highestIndexValue;
public DataPage(DataTable table, SqlDataAdapter adapter, int rowIndex)
{
this.table = table;
this.adapter = adapter;
lowestIndexValue = MapToLowerBoundary(rowIndex);
highestIndexValue = MapToUpperBoundary(rowIndex);
System.Diagnostics.Debug.Assert(lowestIndexValue >= 0);
System.Diagnostics.Debug.Assert(highestIndexValue >= 0);
}
public int LowestIndex
{
get
{
return lowestIndexValue;
}
}
public int HighestIndex
{
get
{
return highestIndexValue;
}
}
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;
}
public DataTable getTable()
{
return this.table;
}
public SqlDataAdapter getAdapter()
{
return this.adapter;
}
}
private DataPage[] cachePages;
private IDataPageRetrieverAll dataSupply;
public CacheAll(IDataPageRetrieverAll dataSupplier, int rowsPerPage)
{
dataSupply = dataSupplier;
CacheAll.RowsPerPage = rowsPerPage;
LoadFirstTwoPages();
}
// 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))
{
element = cachePages[0].table
.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
return true;
}
else if (cachePages.Length > 1)
{
if (IsRowCachedInPage(1, rowIndex))
{
element = cachePages[1].table.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
return true;
}
}
return false;
}
public string RetrieveElement(int rowIndex, int columnIndex)
{
string element = null;
if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
{
return element;
}
else
{
return RetrieveData_CacheIt_ThenReturnElement(
rowIndex, columnIndex);
}
}
private void LoadFirstTwoPages()
{
DataTable table1 = dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(0), RowsPerPage);
SqlDataAdapter adapter1 = dataSupply.getAdapter();
DataTable table2 = dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(RowsPerPage), RowsPerPage);
SqlDataAdapter adapter2 = dataSupply.getAdapter();
cachePages = new DataPage[]{
new DataPage(table1, adapter1, 0),
new DataPage(table2, adapter2, RowsPerPage)};
}
private string RetrieveData_CacheIt_ThenReturnElement(
int rowIndex, int columnIndex)
{
// Retrieve a page worth of data containing the requested value.
DataTable table = dataSupply.SupplyPageOfData(
DataPage.MapToLowerBoundary(rowIndex), RowsPerPage);
SqlDataAdapter adapter = dataSupply.getAdapter();
// Replace the cached page furthest from the requested cell
// with a new page containing the newly retrieved data.
cachePages[GetIndexToUnusedPage(rowIndex)] = new DataPage(table, adapter, rowIndex);
return RetrieveElement(rowIndex, columnIndex);
}
// 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;
}
public DataPage[] getCachePages()
{
return cachePages;
}
}
}
…和DataRetrieverAll.cs类:
using System;
using System.Data.SqlClient;
using System.Data;
namespace ASC.Code.Forms.Helper
{
public class DataRetrieverAll : IDataPageRetrieverAll
{
private string tableName;
private string sortColumn;
private SqlCommand command;
private DataTable table;
private SqlDataAdapter adapter;
public DataRetrieverAll(string connectionString, string tableName, string sortColumn)
{
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
command = connection.CreateCommand();
this.tableName = tableName;
this.sortColumn = sortColumn;
}
private int rowCountValue = -1;
public int RowCount
{
get
{
// Return the existing value if it has already been determined.
if (rowCountValue != -1)
{
return rowCountValue;
}
// Retrieve the row count from the database.
command.CommandText = "SELECT COUNT(*) FROM " + tableName;
rowCountValue = (int)command.ExecuteScalar();
return rowCountValue;
}
}
private DataColumnCollection columnsValue;
public DataColumnCollection Columns
{
get
{
// Return the existing value if it has already been determined.
if (columnsValue != null)
{
return columnsValue;
}
// Retrieve the column information from the database.
command.CommandText = "SELECT * FROM " + tableName;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.FillSchema(table, SchemaType.Source);
columnsValue = table.Columns;
return columnsValue;
}
}
private string commaSeparatedListOfColumnNamesValue = null;
private string CommaSeparatedListOfColumnNames
{
get
{
// Return the existing value if it has already been determined.
if (commaSeparatedListOfColumnNamesValue != null)
{
return commaSeparatedListOfColumnNamesValue;
}
// Store a list of column names for use in the
// SupplyPageOfData method.
System.Text.StringBuilder commaSeparatedColumnNames =
new System.Text.StringBuilder();
bool firstColumn = true;
foreach (DataColumn column in Columns)
{
if (!firstColumn)
{
commaSeparatedColumnNames.Append("], [");
}
else
{
commaSeparatedColumnNames.Append("[");
}
commaSeparatedColumnNames.Append(column.ColumnName);
firstColumn = false;
}
commaSeparatedColumnNames.Append("]");
commaSeparatedListOfColumnNamesValue =
commaSeparatedColumnNames.ToString();
return commaSeparatedListOfColumnNamesValue;
}
}
// Declare variables to be reused by the SupplyPageOfData method.
private string columnToSortBy;
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
// Store the name of the ID column. This column must contain unique
// values so the SQL below will work properly.
if (columnToSortBy == null)
{
columnToSortBy = this.Columns[0].ColumnName;
}
if (!this.Columns[columnToSortBy].Unique)
{
throw new InvalidOperationException(String.Format(
"Column {0} must contain unique values.", columnToSortBy));
}
// Retrieve the specified number of rows from the database, starting
// with the row specified by the lowerPageBoundary parameter.
String text = "Select Top " + rowsPerPage + " " +
CommaSeparatedListOfColumnNames + " From " + tableName +
" WHERE " + columnToSortBy + " NOT IN (SELECT TOP " +
lowerPageBoundary + " " + columnToSortBy + " From " +
tableName + " Order By " + sortColumn +
") Order By " + sortColumn;
command.CommandText = text;
adapter = new SqlDataAdapter(text, GUI.dictSettings["connectionString"]);
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.Fill(table);
return table;
}
public DataTable getDataTable()
{
return table;
}
public SqlDataAdapter getAdapter()
{
return adapter;
}
}
}
那么,我如何填充网格?我使用另一个Helper-Class并从Main GUI类调用Helper-Class中的方法:
Main.cs:
//...
private String columnToSortByAll;
public GUI()
{
InitializeComponent();
init();
}
private void init()
{
helper = new GUIHelper(grid, this);
//Tabellen mit Werten füllen
fillTablesInit();
}
private void fillTablesInit()
{
helper.getData("(SELECT * FROM TOOL_materialSumme WHERE Display IS NULL OR Display = 1)a", "ID", asc_all); //asc_all = Boolean value, indicating sort direction asc /desc
}
private void grid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
String columnName = grid.Columns[e.ColumnIndex].Name;
if (columnName.Equals(columnToSortBy))
{
if (asc_all) asc_all = false; else asc_all = true;
}
else
{
columnToSortBy = columnName;
asc_all = true;
}
helper.getDataALL("(SELECT * FROM TOOL_materialSumme WHERE Display IS NULL OR Display = 1)a", columnToSortBy, asc_all);
}
private void grid_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
String actualValue = helper.getMemoryCache().RetrieveElement(e.RowIndex, e.ColumnIndex);
e.Value = actualValue;
}
private void grid_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
//MessageBox.Show(e.Context.ToString());
}
private void grid_CellValuePushed(object sender, DataGridViewCellValueEventArgs e)
{
String newValue = "";
if (e.Value != null) newValue = e.Value.ToString();
int column = e.ColumnIndex;
ASC.Code.Forms.Helper.CacheAll.DataPage[] pages = helper.getMemoryCache().getCachePages();
DataTable[] tables = new DataTable[2];
SqlDataAdapter[] adapters = new SqlDataAdapter[2];
tables[0] = pages[0].getTable();
tables[1] = pages[1].getTable();
adapters[0] = pages[0].getAdapter();
adapters[1] = pages[1].getAdapter();
String id = grid.Rows[e.RowIndex].Cells["ID"].Value.ToString();
for (int x = 0; x < tables.Length; x++)
{
for (int a = 0; a < tables[x].Rows.Count; a++)
{
String temp = tables[x].Rows[a][column].ToString();
if (tables[x].Rows[a]["ID"].ToString() == id)
{
tables[x].Rows[a][column] = newValue;
adapters[x].Update(tables[x]);
break;
}
}
}
grid.Refresh();
}
//...
GUIHelper.cs:
private DataGridView grid
private CacheAll memoryCache;
private DataRetrieverAll retriever;
public GUIHelper(DataGridView grid, GUI gui)
{
this.gui = gui;
this.grid = grid;
init();
}
private void init()
{
//...
}
public void getData(string selectCommand, string sortColumn, Boolean asc_all)
{
grid.VirtualMode = true;
try
{
if (asc_all) sortColumn = "["+sortColumn + "] ASC"; else sortColumn = "["+sortColumn + "] DESC";
retriever = new DataRetrieverAll("Insert ConnectionString here...", selectCommand, sortColumn);
memoryCache = new CacheAll(retriever, GUI.amountDatasets); //amountDatasets = Amount of Datasets per cached-page
if (grid.Columns.Count == 0)
{
foreach (DataColumn column in retriever.Columns)
{
grid.Columns.Add(column.ColumnName, column.ColumnName);
}
}
grid.Rows.Clear();
grid.RowCount = retriever.RowCount;
grid.Refresh();
}
catch (SqlException)
{
MessageBox.Show("Connection could not be established. " +
"Verify that the connection string is valid.");
Application.Exit();
}
}
public CacheAll getMemoryCache()
{
return memoryCacheAll;
}
嗯,这就是我做的方式,而且效果很好.我最终没有使用它,但仍然有代码……设置grid.VirtualMode = true并为Main类中的网格实现ColumnHeaderMouseClick事件非常重要.快乐的编码:-)