0

I have a DataGrid of about 2500 entries. I want the user to be able to do a search, and for rows that don't contain the search term to be hidden.

Here's my strategy in pseudocode, I'm new to C#, .Net, and WPF, so feel very free to recommend alternative methods.

for each Row in DataGrid
    for each Column in Row
        if Cell doesn't contains SearchQuery
            hide Row
            break

In C#:

        List<int> rowsWithoutMatch = new List<int>();

        for (int i = 0; i < dataGrid.Items.Count; i++)
        {
            DataGridRow row = (DataGridRow)dataGrid.
                              ItemContainerGenerator.ContainerFromIndex(i);

            if (row != null)
            {
                for (int j = 0; j < dataGrid.Columns.Count; j++)
                {
                    DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                    if (presenter != null)
                    {
                        DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(j);

                        if (cell != null)
                        {
                            TextBlock tb = cell.Content as TextBlock;
                            string content = tb.Text;

                            if (!(content).Contains(columnFilters[j]))
                            {
                                row.Visibility = Visibility.Collapsed;
                                break;
                            }
                        }
                    }
                }
            }
        }

I was getting this weird error where row != null only for the first 24 iterations and it was all null after that so it stopped iterating through rows.

I learned from this SO question that it was because there were only 24 rows visible in the DataGrid, and the rows that weren't on screen were all null.

I fixed the problem by putting the tag VirtualizingStackPanel.IsVirtualizing="False" into the DataGrid XMAL, but now it loads all the rows at once and goes extremely slowly. Same with adding dataGrid.UpdateLayout(); dataGrid.ScrollIntoView(dataGrid.Items[i]);, but that also has a few more bugs.

I also tried this, which was faster

        dataGrid.DataContext = dt.AsDataView();

        for (int i = dt.Rows.Count - 1; i >= 0; i--)
        {
            DataRow dr = dt.Rows[i];

            for (int j = 0; j < columnFilters.Length; j++)
            {
                if (! dr[j].ToString().Contains( columnFilters[j] ))
                {
                    dr.Delete();
                    break ;
                }
            }
        }

        dataGrid.DataContext = dt.AsDataView();

But I have a SQL DataBase connected to the DataGrid, and deleting rows became a huge problem. Plus, toggling visibility seemed like a better idea than messing around with the DataTable.

How could I make this go faster? Or even, what's an altogether different/better method of doing what I'm trying to? There seems a lot more options for windows forms for this sort of thing, but it's too late to change back from WPF.

Thanks a lot.

Community
  • 1
  • 1
Charles Clayton
  • 13,212
  • 10
  • 73
  • 114
  • Just bind to a LINQ query output – paparazzo Jan 19 '15 at 19:14
  • @Blam Could you go into more detail? I'm new to C# and am unfamiliar with LINQ – Charles Clayton Jan 19 '15 at 19:16
  • 1
    You shouldn't loop to make the list display. Your SQL query should fill a DataTable in your model and your view should have DataView on that table ONLY showing the fields you need and row you need – Franck Jan 19 '15 at 19:20
  • This should get you started http://msdn.microsoft.com/en-us/library/bb546190(v=vs.110).aspx I there is also collectionviewsource – paparazzo Jan 19 '15 at 19:30

1 Answers1

0

here a simplistic non optimized DataTable to Object simple to query Linq on

public class LinqTable
{

    public LinqTable()
    {

    }

    public LinqTable(DataTable sourceTable)
    {
        LoadFromTable(sourceTable);
    }

    public List<LinqRow> Rows = new List<LinqRow>();

    public List<string> Columns
    {
        get
        {
            var columns = new List<string>();

            if (Rows != null && Rows.Count > 0)
            {                    
                Rows[0].Fields.ForEach(field => columns.Add(field.Name));
            }

            return columns;
        }
    }

    public void LoadFromTable(DataTable sourceTable)
    {
        sourceTable.Rows.Cast<DataRow>().ToList().ForEach(row => Rows.Add(new LinqRow(row)));
    }

    public DataTable AsDataTable()
    {
        var dt = new DataTable("data");

        if (Rows != null && Rows.Count > 0)
        {
            Rows[0].Fields.ForEach(field =>
                {
                    dt.Columns.Add(field.Name, field.DataType);
                });

            Rows.ForEach(row=>
                {
                    var dr = dt.NewRow();

                    row.Fields.ForEach(field => dr[field.Name] = field.Value);

                    dt.Rows.Add(dr);
                });
        }

        return dt;
    }
}

public class LinqRow 
{
    public List<LinqField> Fields = new List<LinqField>();

    public LinqRow()
    {

    }

    public LinqRow(DataRow sourceRow)
    {
        sourceRow.Table.Columns.Cast<DataColumn>().ToList().ForEach(col => Fields.Add(new LinqField(col.ColumnName, sourceRow[col], col.DataType)));
    }

    public object this[int index]
    {
        get
        {
            return Fields[index].Value;
        }
    }
    public object this[string name]
    {
        get
        {
            return Fields.Find(f => f.Name == name).Value;
        }
    }

    public DataTable AsSingleRowDataTable()
    {
        var dt = new DataTable("data");

        if (Fields != null && Fields.Count > 0)
        {
            Fields.ForEach(field =>
            {
                dt.Columns.Add(field.Name, field.DataType);
            });

            var dr = dt.NewRow();

            Fields.ForEach(field => dr[field.Name] = field.Value);

            dt.Rows.Add(dr);
        }

        return dt;
    }
}

public class LinqField
{
    public Type DataType;
    public object Value;
    public string Name;

    public LinqField(string name, object value, Type dataType)
    {
        DataType = dataType;
        Value = value;
        Name = name;
    }

    public LinqField(string name, object value)
    {
        DataType = value.GetType();
        Value = value;
        Name = name;
    }

    public override string ToString()
    {
        return Value.ToString();
    }      
}

calls are very simple and binding as well.

// this create a lighter object for linq.
var myTable = new LinqTable(myDatatable);

// want to see only active clients
var filteredResult = myTable.Rows.Where(field => Convert.ToBoolean(field["IsActive"].ToString()) == true).ToList();

// now i want sort by name
var filteredResult = myTable.Rows.OrderBy(field => field["Name"].ToString()).ToList();

to bind in XAML you can either use the integer or the string indexer

// by int indexer
<Textbox Text="{Binding [2]}" />

// by string indexer
<Textbox Text="{Binding [Name]}" />
Franck
  • 4,167
  • 1
  • 24
  • 48
  • Okay, I'll try this. I'm going to have to change a lot of code though because there's quite a bit going on, like the user can add or remove visible columns, they can edit the datagrid which in turn updates the SQL database. Will I be able to do that stuff using LINQ? – Charles Clayton Jan 19 '15 at 19:40
  • Columns shown would be limited withing the XAML control. you can display 2 columns if you want. You can bind a visibility state on each columns and you only display those you want. – Franck Jan 19 '15 at 19:59