I would like to use custom sorting in Infgragistics.
I read that I can use IComparer.
I have an UltraGridColumnd Bound with string data type. I woule like to sort in by value from another column, which is long data type.
Is it possible?
Yes, this is possible and can be achieved exactly using IComparer interface. Each UltraGrid column has SortComparer property on which can be assigned object that implements IComparer interface. As written in the documentation about the SortComparer property:
Property used to perform custom sort comparisons when sorting rows by
this column. The values passed in the Compare method of the IComparer
will be two UltraGridCell objects.
Here is code snippet regarding your scenario as the comparison values comes from another column.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ultraGrid1.DataSource = InitializeDataSource(10);
ultraGrid1.DisplayLayout.Override.HeaderClickAction = HeaderClickAction.SortMulti;
ultraGrid1.DisplayLayout.Bands[0].Columns[0].SortComparer = new CustomComparer();
}
private DataTable InitializeDataSource(int rows)
{
DataTable table = new DataTable();
table.Columns.Add("String Column", typeof(string));
table.Columns.Add("Long Column", typeof(long));
for (int index = 0; index < rows; index++)
{
table.Rows.Add(new object[] { "Text", index });
}
return table;
}
}
public class CustomComparer : IComparer
{
public int Compare(object x, object y)
{
var valueColumn = "Long Column";
var firstCell = x as UltraGridCell;
var secondCell = y as UltraGridCell;
var firstCellValue = (long)firstCell.Row.Cells[valueColumn].Value;
var secondCellValue = (long)secondCell.Row.Cells[valueColumn].Value;
if (firstCellValue == secondCellValue)
{
return 0;
}
else if (firstCellValue > secondCellValue)
{
return -1;
}
else
{
return 1;
}
}
}
Related
I am not very experienced with Windows Forms and am not pretty sure how I should tackle with this task the best way possible. I have a class which looks like this:
public class VariableMapping
{
private string variableName;
private string variableText;
private string variableSelector;
public VariableMapping(string variableName, string variableText, string variableSelector)
{
this.VariableName = variableName;
this.VariableText = variableText;
this.VariableSelector = variableSelector;
}
public string VariableName
{
get { return this.variableName; }
set { this.variableName = value; }
}
public string VariableText
{
get { return this.variableText; }
set { this.variableText = value; }
}
public string VariableSelector
{
get { return this.variableSelector; }
set { this.variableSelector = value; }
}
}
I want to create a DataGridView which should be bound to a number of elements of type VariableMapping in a list. However, I want only 1 of the properties(VariableText) of every instance to be shown in the DataGridView but I want to be able to address the whole object through the DataGrid when I need to. I also need to add 2 more custom columns: a ComboBox with predefined values and a NumberBox.
It might seem a really simple task but I'm trully unexperienced in WinForms and couldn't find a solution I can use already. Thank you!
Edit: I am trying something like this but it doesn't seem to work properly:
public partial class MappingTable : Form
{
private DataGridView dataGridView1 = new DataGridView();
public MappingTable(List<VariableMapping> variableMappings)
{
InitializeComponent();
var colors = new List<string>() { "#color_k1", "#color_k2", "#color_s1" };
dataGridView1.AutoGenerateColumns = false;
dataGridView1.AutoSize = true;
dataGridView1.DataSource = variableMappings;
DataGridViewColumn titleColumn = new DataGridViewColumn();
titleColumn.DataPropertyName = "VariableText";
titleColumn.HeaderText = "Variable";
titleColumn.Name = "Variable*";
dataGridView1.Columns.Add(titleColumn);
DataGridViewComboBoxColumn colorsColumn = new DataGridViewComboBoxColumn();
colorsColumn.DataSource = colors;
colorsColumn.HeaderText = "Color";
dataGridView1.Columns.Add(colorsColumn);
DataGridViewTextBoxColumn opacityColumn = new DataGridViewTextBoxColumn();
opacityColumn.HeaderText = "Opacity";
dataGridView1.Columns.Add(opacityColumn);
this.Controls.Add(dataGridView1);
this.AutoSize = true;
}
}
I'm trying to use the SQL function CONSTAINS to filter some data on QueryOver API.
The main issue is i can't use SqlFunction in where clause, it does not compile, because a ICriterion is needed.
var result = Session.QueryOver<Individual>()
.Where(Projections.SqlFunction(
"FullTextContains", NHibernateUtil.Boolean,
Projections.Property<Individual>(x => x.LastName),
Projections.Constant("something")))
.List();
I tried to match it to a TRUE constant, but when the query is executed it generates syntax error, because CONSTAINS function can't be used with equals operator.
var result = Session.QueryOver<Individual>()
.Where(Restrictions.Eq(Projections.SqlFunction(
"FullTextContains", NHibernateUtil.Boolean,
Projections.Property<Individual>(p => p.LastName),
Projections.Constant("something")), true))
.List();
How can i use a boolean sql function directly in where expression on QueryOver API?
This is my finding for letting QueryOver support it:
var projection = Projections.SqlFunction("FullTextContains",
NHibernateUtil.Boolean,
Projections.Property<Individual>(x => x.LastName),
Projections.Constant("something"));
var result = Session.QueryOver<Individual>()
.Where(new ProjectionAsCriterion(projection))
.List();
To use a IProjection as a ICriterion I created my own implementation based on SimpleExpression class from NHibernate project.
public class ProjectionAsCriterion : AbstractCriterion
{
private readonly IProjection _projection;
public ProjectionAsCriterion(IProjection projection)
{
_projection = projection;
}
public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery,
IDictionary<string, IFilter> enabledFilters)
{
var columnNames = CriterionUtil.GetColumnNamesForSimpleExpression(
null, _projection, criteriaQuery, criteria, enabledFilters, this, string.Empty);
var sqlBuilder = new SqlStringBuilder(4 * columnNames.Length);
for (int i = 0; i < columnNames.Length; i++)
{
if (i > 0)
{
sqlBuilder.Add(" and ");
}
sqlBuilder.Add(columnNames[i]);
}
return sqlBuilder.ToSqlString();
}
public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
{
var typedValues = new List<TypedValue>();
if (_projection != null)
{
typedValues.AddRange(_projection.GetTypedValues(criteria, criteriaQuery));
}
typedValues.Add(GetParameterTypedValue(criteria, criteriaQuery));
return typedValues.ToArray();
}
private TypedValue GetParameterTypedValue(ICriteria criteria, ICriteriaQuery criteriaQuery)
{
return CriterionUtil.GetTypedValues(criteriaQuery, criteria, _projection, null).Single();
}
public override IProjection[] GetProjections()
{
return new[] { _projection };
}
public override string ToString()
{
return _projection.ToString();
}
}
My WPF application generates sets of data which may have a different number of columns each time. Included in the output is a description of each column that will be used to apply formatting. A simplified version of the output might be something like:
class Data
{
IList<ColumnDescription> ColumnDescriptions { get; set; }
string[][] Rows { get; set; }
}
This class is set as the DataContext on a WPF DataGrid but I actually create the columns programmatically:
for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = data.ColumnDescriptions[i].Name,
Binding = new Binding(string.Format("[{0}]", i))
});
}
Is there any way to replace this code with data bindings in the XAML file instead?
Here's a workaround for Binding Columns in the DataGrid. Since the Columns property is ReadOnly, like everyone noticed, I made an Attached Property called BindableColumns which updates the Columns in the DataGrid everytime the collection changes through the CollectionChanged event.
If we have this Collection of DataGridColumn's
public ObservableCollection<DataGridColumn> ColumnCollection
{
get;
private set;
}
Then we can bind BindableColumns to the ColumnCollection like this
<DataGrid Name="dataGrid"
local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
AutoGenerateColumns="False"
...>
The Attached Property BindableColumns
public class DataGridColumnsBehavior
{
public static readonly DependencyProperty BindableColumnsProperty =
DependencyProperty.RegisterAttached("BindableColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridColumnsBehavior),
new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = source as DataGrid;
ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
dataGrid.Columns.Clear();
if (columns == null)
{
return;
}
foreach (DataGridColumn column in columns)
{
dataGrid.Columns.Add(column);
}
columns.CollectionChanged += (sender, e2) =>
{
NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
if (ne.Action == NotifyCollectionChangedAction.Reset)
{
dataGrid.Columns.Clear();
foreach (DataGridColumn column in ne.NewItems)
{
dataGrid.Columns.Add(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Add)
{
foreach (DataGridColumn column in ne.NewItems)
{
dataGrid.Columns.Add(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Move)
{
dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
}
else if (ne.Action == NotifyCollectionChangedAction.Remove)
{
foreach (DataGridColumn column in ne.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Replace)
{
dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
}
};
}
public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
{
element.SetValue(BindableColumnsProperty, value);
}
public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
{
return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
}
}
I've continued my research and have not found any reasonable way to do this. The Columns property on the DataGrid isn't something I can bind against, in fact it's read only.
Bryan suggested something might be done with AutoGenerateColumns so I had a look. It uses simple .Net reflection to look at the properties of the objects in ItemsSource and generates a column for each one. Perhaps I could generate a type on the fly with a property for each column but this is getting way off track.
Since this problem is so easily sovled in code I will stick with a simple extension method I call whenever the data context is updated with new columns:
public static void GenerateColumns(this DataGrid dataGrid, IEnumerable<ColumnSchema> columns)
{
dataGrid.Columns.Clear();
int index = 0;
foreach (var column in columns)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = column.Name,
Binding = new Binding(string.Format("[{0}]", index++))
});
}
}
// E.g. myGrid.GenerateColumns(schema);
I have found a blog article by Deborah Kurata with a nice trick how to show variable number of columns in a DataGrid:
Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM
Basically, she creates a DataGridTemplateColumn and puts ItemsControl inside that displays multiple columns.
I managed to make it possible to dynamically add a column using just a line of code like this:
MyItemsCollection.AddPropertyDescriptor(
new DynamicPropertyDescriptor<User, int>("Age", x => x.Age));
Regarding to the question, this is not a XAML-based solution (since as mentioned there is no reasonable way to do it), neither it is a solution which would operate directly with DataGrid.Columns. It actually operates with DataGrid bound ItemsSource, which implements ITypedList and as such provides custom methods for PropertyDescriptor retrieval. In one place in code you can define "data rows" and "data columns" for your grid.
If you would have:
IList<string> ColumnNames { get; set; }
//dict.key is column name, dict.value is value
Dictionary<string, string> Rows { get; set; }
you could use for example:
var descriptors= new List<PropertyDescriptor>();
//retrieve column name from preprepared list or retrieve from one of the items in dictionary
foreach(var columnName in ColumnNames)
descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName]))
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors)
and your grid using binding to MyItemsCollection would be populated with corresponding columns. Those columns can be modified (new added or existing removed) at runtime dynamically and grid will automatically refresh it's columns collection.
DynamicPropertyDescriptor mentioned above is just an upgrade to regular PropertyDescriptor and provides strongly-typed columns definition with some additional options. DynamicDataGridSource would otherwise work just fine event with basic PropertyDescriptor.
Made a version of the accepted answer that handles unsubscription.
public class DataGridColumnsBehavior
{
public static readonly DependencyProperty BindableColumnsProperty =
DependencyProperty.RegisterAttached("BindableColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridColumnsBehavior),
new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
/// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary>
private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers;
static DataGridColumnsBehavior()
{
_handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();
}
private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = source as DataGrid;
ObservableCollection<DataGridColumn> oldColumns = e.OldValue as ObservableCollection<DataGridColumn>;
if (oldColumns != null)
{
// Remove all columns.
dataGrid.Columns.Clear();
// Unsubscribe from old collection.
NotifyCollectionChangedEventHandler h;
if (_handlers.TryGetValue(dataGrid, out h))
{
oldColumns.CollectionChanged -= h;
_handlers.Remove(dataGrid);
}
}
ObservableCollection<DataGridColumn> newColumns = e.NewValue as ObservableCollection<DataGridColumn>;
dataGrid.Columns.Clear();
if (newColumns != null)
{
// Add columns from this source.
foreach (DataGridColumn column in newColumns)
dataGrid.Columns.Add(column);
// Subscribe to future changes.
NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid);
_handlers[dataGrid] = h;
newColumns.CollectionChanged += h;
}
}
static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid)
{
switch (ne.Action)
{
case NotifyCollectionChangedAction.Reset:
dataGrid.Columns.Clear();
foreach (DataGridColumn column in ne.NewItems)
dataGrid.Columns.Add(column);
break;
case NotifyCollectionChangedAction.Add:
foreach (DataGridColumn column in ne.NewItems)
dataGrid.Columns.Add(column);
break;
case NotifyCollectionChangedAction.Move:
dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
foreach (DataGridColumn column in ne.OldItems)
dataGrid.Columns.Remove(column);
break;
case NotifyCollectionChangedAction.Replace:
dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
break;
}
}
public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
{
element.SetValue(BindableColumnsProperty, value);
}
public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
{
return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
}
}
You can create a usercontrol with the grid definition and define 'child' controls with varied column definitions in xaml. The parent needs a dependency property for columns and a method for loading the columns:
Parent:
public ObservableCollection<DataGridColumn> gridColumns
{
get
{
return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
}
set
{
SetValue(ColumnsProperty, value);
}
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("gridColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(parentControl),
new PropertyMetadata(new ObservableCollection<DataGridColumn>()));
public void LoadGrid()
{
if (gridColumns.Count > 0)
myGrid.Columns.Clear();
foreach (DataGridColumn c in gridColumns)
{
myGrid.Columns.Add(c);
}
}
Child Xaml:
<local:parentControl x:Name="deGrid">
<local:parentControl.gridColumns>
<toolkit:DataGridTextColumn Width="Auto" Header="1" Binding="{Binding Path=.}" />
<toolkit:DataGridTextColumn Width="Auto" Header="2" Binding="{Binding Path=.}" />
</local:parentControl.gridColumns>
</local:parentControl>
And finally, the tricky part is finding where to call 'LoadGrid'.
I am struggling with this but got things to work by calling after InitalizeComponent in my window constructor (childGrid is x:name in window.xaml):
childGrid.deGrid.LoadGrid();
Related blog entry
You might be able to do this with AutoGenerateColumns and a DataTemplate. I'm not positive if it would work without a lot of work, you would have to play around with it. Honestly if you have a working solution already I wouldn't make the change just yet unless there's a big reason. The DataGrid control is getting very good but it still needs some work (and I have a lot of learning left to do) to be able to do dynamic tasks like this easily.
There is a sample of the way I do programmatically:
public partial class UserControlWithComboBoxColumnDataGrid : UserControl
{
private Dictionary<int, string> _Dictionary;
private ObservableCollection<MyItem> _MyItems;
public UserControlWithComboBoxColumnDataGrid() {
_Dictionary = new Dictionary<int, string>();
_Dictionary.Add(1,"A");
_Dictionary.Add(2,"B");
_MyItems = new ObservableCollection<MyItem>();
dataGridMyItems.AutoGeneratingColumn += DataGridMyItems_AutoGeneratingColumn;
dataGridMyItems.ItemsSource = _MyItems;
}
private void DataGridMyItems_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var desc = e.PropertyDescriptor as PropertyDescriptor;
var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute;
if (att != null)
{
if (att.Name == "My Combobox Item") {
var comboBoxColumn = new DataGridComboBoxColumn {
DisplayMemberPath = "Value",
SelectedValuePath = "Key",
ItemsSource = _ApprovalTypes,
SelectedValueBinding = new Binding( "Bazinga"),
};
e.Column = comboBoxColumn;
}
}
}
}
public class MyItem {
public string Name{get;set;}
[ColumnName("My Combobox Item")]
public int Bazinga {get;set;}
}
public class ColumnNameAttribute : Attribute
{
public string Name { get; set; }
public ColumnNameAttribute(string name) { Name = name; }
}
I want to filter a ObservableCollection with max 3000 items in a DataGrid with 6 columns. The user should be able to filter in an "&&"-way all 6 columns.
Should I use LINQ or a CollectionView for it? LINQ seemed faster trying some www samples. Do you have any pro/cons?
UPDATE:
private ObservableCollection<Material> _materialList;
private ObservableCollection<Material> _materialListInternal;
public MaterialBrowserListViewModel()
{
_materialListInternal = new ObservableCollection<Material>();
for (int i = 0; i < 2222; i++)
{
var mat = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Excel Sheet" + i,
Keywords = "financial budget report",
SchoolclassCode = "1",
};
_materialListInternal.Add(mat);
var mat1 = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Word Doc" + i,
Keywords = "Economical staticstics report",
SchoolclassCode = "2",
};
_materialListInternal.Add(mat1);
}
MaterialList = CollectionViewSource.GetDefaultView(MaterialListInternal);
MaterialList.Filter = new Predicate<object>(ContainsInFilter);
}
public bool ContainsInFilter(object item)
{
if (String.IsNullOrEmpty(FilterKeywords))
return true;
Material material = item as Material;
if (DocumentHelper.ContainsCaseInsensitive(material.Keywords,FilterKeywords,StringComparison.CurrentCultureIgnoreCase))
return true;
else
return false;
}
private string _filterKeywords;
public string FilterKeywords
{
get { return _filterKeywords; }
set
{
if (_filterKeywords == value)
return;
_filterKeywords = value;
this.RaisePropertyChanged("FilterKeywords");
MaterialList.Refresh();
}
}
public ICollectionView MaterialList { get; set; }
public ObservableCollection<Material> MaterialListInternal
{
get { return _materialListInternal; }
set
{
_materialListInternal = value;
this.RaisePropertyChanged("MaterialList");
}
}
Using ICollectionView gives you automatic collection changed notifications when you call Refresh. Using LINQ you'll need to fire your own change notifications when the filter needs to be re-run to update the UI. Not difficult, but requires a little more thought than just calling Refresh.
LINQ is more flexible that the simple yes/no filtering used by ICollectionView, but if you're not doing something complex there's not really any advantage to that flexibility.
As Henk stated, there shouldn't be a noticable performance difference in the UI.
For an interactive (DataGrid?) experience you should probabaly use the CollectionView. For a more code-oriented sorting, LINQ.
And with max 3000 items, speed should not be a (major) factor in a UI.
How about both? Thomas Levesque built a LINQ-enabled wrapper around ICollectionView.
Usage:
IEnumerable<Person> people;
// Using query comprehension
var query =
from p in people.ShapeView()
where p.Age >= 18
orderby p.LastName, p.FirstName
group p by p.Country;
query.Apply();
// Using extension methods
people.ShapeView()
.Where(p => p.Age >= 18)
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName)
.Apply();
Code:
public static class CollectionViewShaper
{
public static CollectionViewShaper<TSource> ShapeView<TSource>(this IEnumerable<TSource> source)
{
var view = CollectionViewSource.GetDefaultView(source);
return new CollectionViewShaper<TSource>(view);
}
public static CollectionViewShaper<TSource> Shape<TSource>(this ICollectionView view)
{
return new CollectionViewShaper<TSource>(view);
}
}
public class CollectionViewShaper<TSource>
{
private readonly ICollectionView _view;
private Predicate<object> _filter;
private readonly List<SortDescription> _sortDescriptions = new List<SortDescription>();
private readonly List<GroupDescription> _groupDescriptions = new List<GroupDescription>();
public CollectionViewShaper(ICollectionView view)
{
if (view == null)
throw new ArgumentNullException("view");
_view = view;
_filter = view.Filter;
_sortDescriptions = view.SortDescriptions.ToList();
_groupDescriptions = view.GroupDescriptions.ToList();
}
public void Apply()
{
using (_view.DeferRefresh())
{
_view.Filter = _filter;
_view.SortDescriptions.Clear();
foreach (var s in _sortDescriptions)
{
_view.SortDescriptions.Add(s);
}
_view.GroupDescriptions.Clear();
foreach (var g in _groupDescriptions)
{
_view.GroupDescriptions.Add(g);
}
}
}
public CollectionViewShaper<TSource> ClearGrouping()
{
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearSort()
{
_sortDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearFilter()
{
_filter = null;
return this;
}
public CollectionViewShaper<TSource> ClearAll()
{
_filter = null;
_sortDescriptions.Clear();
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> Where(Func<TSource, bool> predicate)
{
_filter = o => predicate((TSource)o);
return this;
}
public CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> OrderByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Descending);
}
public CollectionViewShaper<TSource> ThenBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> ThenByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Descending);
}
private CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector, bool clear, ListSortDirection direction)
{
string path = GetPropertyPath(keySelector.Body);
if (clear)
_sortDescriptions.Clear();
_sortDescriptions.Add(new SortDescription(path, direction));
return this;
}
public CollectionViewShaper<TSource> GroupBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
string path = GetPropertyPath(keySelector.Body);
_groupDescriptions.Add(new PropertyGroupDescription(path));
return this;
}
private static string GetPropertyPath(Expression expression)
{
var names = new Stack<string>();
var expr = expression;
while (expr != null && !(expr is ParameterExpression) && !(expr is ConstantExpression))
{
var memberExpr = expr as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("The selector body must contain only property or field access expressions");
names.Push(memberExpr.Member.Name);
expr = memberExpr.Expression;
}
return String.Join(".", names.ToArray());
}
}
Credit:
http://www.thomaslevesque.com/2011/11/30/wpf-using-linq-to-shape-data-in-a-collectionview/
Based on a visual complexity and number of items there really WILL be a noticable performance difference since the Refresh method recreates the whole view!!!
You need my ObservableComputations library. Using this library you can code like this:
ObservableCollection<Material> MaterialList = MaterialListInternal.Filtering(m =>
String.IsNullOrEmpty(FilterKeywords)
|| DocumentHelper.ContainsCaseInsensitive(
material.Keywords, FilterKeywords, StringComparison.CurrentCultureIgnoreCase));
MaterialList reflects all the changes in the MaterialListInternal collection. Do not forget to add the implementation of the INotifyPropertyChanged interface to Material class, so that MaterialList collection reflects the changes in material.Keywords property.
I'm trying to bind a List<T> to a DataGridView control, and I'm not having any luck creating custom bindings.
I have tried:
gvProgramCode.DataBindings.Add(new Binding("Opcode",code,"Opcode"));
It throws an exception, saying that nothing was found by that property name.
The name of the column in question is "Opcode". The name of the property in the List<T> is Opcode.
ANSWER EDIT: the problem was that I did not have the bindable fields in my class as properties, just public fields...Apparently it doesn't reflect on fields, just properties.
Is the property on the grid you are binding to Opcode as well?.. if you want to bind directly to List you would just DataSource = list. The databindings allows custom binding. are you trying to do something other than the datasource?
You are getting a bunch of empty rows? do the auto generated columns have names? Have you verified data is in the object (not just string.empty) ?
class MyObject
{
public string Something { get; set; }
public string Text { get; set; }
public string Other { get; set; }
}
public Form1()
{
InitializeComponent();
List<MyObject> myList = new List<MyObject>();
for (int i = 0; i < 200; i++)
{
string num = i.ToString();
myList.Add(new MyObject { Something = "Something " + num , Text = "Some Row " + num , Other = "Other " + num });
}
dataGridView1.DataSource = myList;
}
this should work fine...
I can't really tell what you're trying to do with the example you included, but binding to a generic list of objects is fairly straightforward if you just want to list the objects:
private BindingSource _gridSource;
private BindingSource GridSource
{
get
{
if (_gridSource == null)
_gridSource = new BindingSource();
return _gridSource;
}
}
private void Form1_Load(object sender, EventArgs e)
{
List<FluffyBunny> list = new List<FluffyBunny>();
list.Add(new FluffyBunny { Color = "White", EarType = "Long", Name = "Stan" });
list.Add(new FluffyBunny { Color = "Brown", EarType = "Medium", Name = "Mike" });
list.Add(new FluffyBunny { Color = "Mottled", EarType = "Short", Name = "Torvald" });
GridSource.DataSource = list;
dataGridView1.Columns["EarType"].Visible = false; //Optionally hide a column
dataGridView1.DataSource = GridSource;
}
If you only want to display specific properties of the List's type you should be able to make the unwanted column(s) invisible.
Technically, you don't really need to create the BindingSource, but I find it's a whole lot easier when I'm doing updates or changes if I have it.
Hope this helps.
Had the same issue... I had a struct with public fields obviously. nothing in the grid. provided public getters, worked.
Another solution I've found is to use the BindingList collection.
private void Form1_Load(object sender, EventArgs e)
{
BindingList people= new BindingList {
new Person {Name="John",Age=23},
new Person {Name="Lucy",Age=16}
};
dataGridView1.DataSource= people;
}
It works fine for me,