Reputation: 17976
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.
Upvotes: 0
Views: 2423
Reputation: 4440
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]}" />
Upvotes: 0