C# DataGridView virtual mode: Enable sorting

99封情书 提交于 2019-12-23 14:21:14

问题


Is there a way to sort a DataGridView in virtual mode?

I have implemented a Gridview in virtual mode following this microsoft example: http://msdn.microsoft.com/de-de/library/ms171624.aspx . Also I have modified the example to be able to write data to database. This works out fine and the virtual mode gives a huge increase in speed, but my customer needs to sort the columns.

After searching the web for a while I found the Link http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/25b3f7c3-95b6-4c49-802b-b5a2a62915ac , but can´t get it to work.

Could anybody point out how if there is way to sort columns in virtual mode and if so, how I would have to do it?

Thanks a lot in advance!


回答1:


Ok, I´ve solved this now. Using the microsoft example ( http://msdn.microsoft.com/de-de/library/ms171624.aspx ) I modified the DataRetrieverLog class to receive an additional value in the constructor: The column name to sort by (this includes the sort direction, e.g. "Name ASC"). For anyone interested, here´s the modified code:

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;
    }
}

In addition to that, I implemented the ColumnHeaderMouseClick-Event in my Gridview. In that I get the column name and the sort direction and then reload all underlying table data by creating a new DataRetrieverLog instance and passing the column name + sort direction. Next I just refresh the Gridview: gridView.Refresh()

Thats it.

EDIT (20.07.2015):

To make things a bit clearer, here´s the full way of getting a sortable DataGridView in Virtual Mode to work. It´s been a while since I was playing around with this, so I hope I can make it clear enough for you to get it working and haven´t missed out on anything. Three Helper classes are needed to get this to work:

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;
        }

    }
}

…and the DataRetrieverAll.cs class:

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;
        }
    }
}

So, how do I fill the Grid? I use another Helper-Class and call a method within the Helper-Class from the Main GUI- 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;
}

Well, that´s the way I did it and it worked out well. I ended up not using it, but still have the code... It´s important to set grid.VirtualMode = true and to implement the ColumnHeaderMouseClick event for the grid in the Main class. Happy coding :-)



来源:https://stackoverflow.com/questions/17087074/c-sharp-datagridview-virtual-mode-enable-sorting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!