I am creating a List<> of a Test class, I made. I then convert it to an array and assign it to a propertygrid control.
List<Test> lc_Test;
public class Test : UserControl
{
[Category("lc_Test"), Description("Testing using List<>, for storage."), DisplayName("Images")]
public Image img { get; set; }
}
// Above in Form1(), I create it
// lc_Test = new List<Test>();
private void button7_Click(object sender, EventArgs e)
{
lc_Test.Clear();
lc_Test.Add(new Test());
lc_Test.Add(new Test());
lc_Test[0].img = pb1.Image;
lc_Test[1].img = pb2.Image;
MessageBox.Show(lc_Test.Count.ToString());
pgTest.SelectedObject = lc_Test.ToArray();
}
As seen in the picture, it works:
I am now wondering, if there is any way to change the Display Name of each item. Cause, it names them "(0)" and "(1)". In this test, I'd like to change it to say. "Test Item 0" and "Test Item 1". I need to change the "Help Text" for each item, also. Got to be a way.
Anyone, need anything from me let me know.
Solution:
Your form1.cs code file:
using System;
using System.Windows.Forms;
namespace PropertyGridSample
{
public class Form1 : System.Windows.Forms.Form
{
Layers layers_test;
internal System.Windows.Forms.PropertyGrid PropertyGrid1;
private System.ComponentModel.Container components = null; //Required designer variable
public Form1()
{
InitializeComponent();
layers_test = new Layers();
PropertyGrid1.SelectedObject = layers_test;
}
// Clean up any resources being used
protected override void Dispose( bool disposing )
{
if (disposing) { if (components != null) { components.Dispose(); } }
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.PropertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.SuspendLayout();
//
// PropertyGrid1
//
this.PropertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.PropertyGrid1.LineColor = System.Drawing.SystemColors.ScrollBar;
this.PropertyGrid1.Location = new System.Drawing.Point(0, -1);
this.PropertyGrid1.Name = "PropertyGrid1";
this.PropertyGrid1.PropertySort = System.Windows.Forms.PropertySort.Alphabetical;
this.PropertyGrid1.Size = new System.Drawing.Size(408, 254);
this.PropertyGrid1.TabIndex = 1;
this.PropertyGrid1.ToolbarVisible = false;
this.PropertyGrid1.SelectedGridItemChanged += new System.Windows.Forms.SelectedGridItemChangedEventHandler(this.PropertyGrid1_SelectedGridItemChanged);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(408, 254);
this.Controls.Add(this.PropertyGrid1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.Name = "Form1";
this.Text = "Customizing Collections in Property Grid Demo";
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void PropertyGrid1_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
PropertyGrid1.Refresh();
}
}
}
I made a separate cs file, called LayerCollection.cs:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
namespace PropertyGridSample
{
[TypeConverter(typeof(LayerConverter))]
public class Layer
{
public Image image { get; set; }
public Layer() { }
}
public class Layers
{
LayerCollection layercollection = new LayerCollection();
public Layers()
{
Layer[] layz = new Layer[2];
Layer lay1 = new Layer(); Layer lay2 = new Layer(); //Create two test layers and add them to the layer collection
layercollection.Add(lay1); layercollection.Add(lay2);
}
[TypeConverter(typeof(LayerCollectionConverter))]
public LayerCollection Layer_Collection { get { return layercollection; } }
}
public class LayerCollection : CollectionBase, ICustomTypeDescriptor
{
#region collection impl
public void Add(Layer lay) { List.Add(lay); } //Adds a layer object to the collection
public void Remove(Layer lay) { List.Remove(lay); } //Removes a layer object from the collection
public Layer this[int index] { get { return (List.Count > -1 && index < List.Count) ? (Layer)List[index] : null; } } //Return a layer object at index position
#endregion
#region ICustomTypeDescriptor impl
public AttributeCollection GetAttributes() { return TypeDescriptor.GetAttributes(this, true); }
public String GetClassName() { return TypeDescriptor.GetClassName(this, true); }
public String GetComponentName() { return TypeDescriptor.GetComponentName(this, true); }
public TypeConverter GetConverter() { return TypeDescriptor.GetConverter(this, true); }
public EventDescriptor GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, true); }
public PropertyDescriptor GetDefaultProperty() { return TypeDescriptor.GetDefaultProperty(this, true); }
public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); }
public EventDescriptorCollection GetEvents() { return TypeDescriptor.GetEvents(this, true); }
public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); }
public object GetPropertyOwner(PropertyDescriptor pd) { return this; }
//Called to get the properties of this type. Returns properties with certain attributes. this restriction is not implemented here.
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); }
//Called to get the properties of this type.
public PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null); // Create a collection object to hold property descriptors
// Iterate the list of layers and create a property descriptor for each layer item and add to the property descriptor collection
for (int i = 0; i < this.List.Count; i++) { LayerCollectionPropertyDescriptor pd = new LayerCollectionPropertyDescriptor(this, i); pds.Add(pd); }
return pds; // return the descriptor collection
}
#endregion
}
class LayerConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is Layer) ? "Layer Data": base.ConvertTo(context, culture, value, destType);
}
}
class LayerCollectionConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is LayerCollection) ? "Items": base.ConvertTo(context, culture, value, destType);
}
}
public class LayerCollectionPropertyDescriptor : PropertyDescriptor
{
private LayerCollection collection = null;
private int index = -1;
public LayerCollectionPropertyDescriptor(LayerCollection coll, int idx) : base("#" + idx.ToString(), null)
{
collection = coll; index = idx;
}
public override AttributeCollection Attributes { get { return new AttributeCollection(null); } }
public override bool CanResetValue(object component) { return true; }
public override bool IsReadOnly { get { return false; } }
public override bool ShouldSerializeValue(object component) { return true; }
public override string Description { get { return "Layer Description"; } }
public override string DisplayName { get { return "Layer " + index.ToString(); } }
public override string Name { get { return "#" + index.ToString(); } }
public override object GetValue(object component) { return collection[index]; }
public override Type ComponentType { get { return collection.GetType(); } }
public override Type PropertyType { get { return collection[index].GetType(); } }
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { } // this.collection[index] = value;
}
}
On your form, you just need to throw a propertygrid on it. I'm still looking at ways to improve it and need the ability to trap the add and remove buttons, on the built in collection editor
Related
UWP Community Toolkit's (now known as Windows Community Toolkit) AdvancedCollectionView collection implements the ISupportIncrementalLoading interface, and so I'm trying to use it with my ListView to only load portions of items at one time, but the ListView still loads all items at once. What am I missing?
Here is the XAML:
<ListView x:Name="MyListView"
DataFetchSize="10"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="10"
ItemSource="{x:Bind ACV, Mode=TwoWay}">
</ListView>
And here is the code-behind:
public class MainPage
{
public AdvancedCollectionView ACV { get; set; }
// Lets say that DocCollection contains 1000 items
public ObservableCollection<Document> DocCollection;
public MainPage()
{
ACV = new AdvancedCollectionView(DocCollection, true);
}
}
The AdvancedCollectionView is a collection view implementation that support filtering, sorting and incremental loading. But according to the usage section of the document I linked,
incremental loading: if your source collection supports the feature then AdvancedCollectionView will do as well (it simply forwards the calls)
So that the AdvancedCollectionView does't have incremental feature itself, it simple forwards the calls. That means the source collection you provided for the AdvancedCollectionView should inherit from ISupportIncrementalLoading interface. Also if you check the AdvancedCollectionView.LoadMoreItemsAsync method, it shows Not implemented yet which indicates AdvancedCollectionView doesn't implement the ISupportIncrementalLoading interface.
And in your case, you just use ObservableCollection for the source collection that doesn't support ISupportIncrementalLoading by default. For creating collection views that support incremental loading please reference this official sample.
DocCollection = new GeneratorIncrementalLoadingClass<DataTest>(1000, (count) =>
{
return new DataTest() { Country = "Ghana" + count, City = "Wa" + count };
});
DocCollection.CollectionChanged += DocCollection_CollectionChanged;
ACV = new AdvancedCollectionView(DocCollection, true);
MyListView.ItemsSource = ACV;
public class GeneratorIncrementalLoadingClass<T> : IncrementalLoadingBase
{
public GeneratorIncrementalLoadingClass(uint maxCount, Func<int, T> generator)
{
_generator = generator;
_maxCount = maxCount;
}
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
{
uint toGenerate = System.Math.Min(count, _maxCount - _count);
// Wait for work
await Task.Delay(10);
// This code simply generates
var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
select (object)_generator(j);
_count += toGenerate;
return values.ToArray();
}
protected override bool HasMoreItemsOverride()
{
return _count < _maxCount;
}
#region State
Func<int, T> _generator;
uint _count = 0;
uint _maxCount;
#endregion
}
public abstract class IncrementalLoadingBase : IList, ISupportIncrementalLoading, INotifyCollectionChanged
{
#region IList
public int Add(object value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(object value)
{
return _storage.Contains(value);
}
public int IndexOf(object value)
{
return _storage.IndexOf(value);
}
public void Insert(int index, object value)
{
throw new NotImplementedException();
}
public bool IsFixedSize
{
get { return false; }
}
public bool IsReadOnly
{
get { return true; }
}
public void Remove(object value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public object this[int index]
{
get
{
return _storage[index];
}
set
{
throw new NotImplementedException();
}
}
public void CopyTo(Array array, int index)
{
((IList)_storage).CopyTo(array, index);
}
public int Count
{
get { return _storage.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { throw new NotImplementedException(); }
}
public IEnumerator GetEnumerator()
{
return _storage.GetEnumerator();
}
#endregion
#region ISupportIncrementalLoading
public bool HasMoreItems
{
get { return HasMoreItemsOverride(); }
}
public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
if (_busy)
{
throw new InvalidOperationException("Only one operation in flight at a time");
}
_busy = true;
return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region Private methods
async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count)
{
try
{
var items = await LoadMoreItemsOverrideAsync(c, count);
var baseIndex = _storage.Count;
_storage.AddRange(items);
// Now notify of the new items
NotifyOfInsertedItems(baseIndex, items.Count);
return new LoadMoreItemsResult { Count = (uint)items.Count };
}
finally
{
_busy = false;
}
}
void NotifyOfInsertedItems(int baseIndex, int count)
{
if (CollectionChanged == null)
{
return;
}
for (int i = 0; i < count; i++)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _storage[i + baseIndex], i + baseIndex);
CollectionChanged(this, args);
}
}
#endregion
#region Overridable methods
protected abstract Task<IList<object>> LoadMoreItemsOverrideAsync(CancellationToken c, uint count);
protected abstract bool HasMoreItemsOverride();
#endregion
#region State
List<object> _storage = new List<object>();
bool _busy = false;
#endregion
}
I'm building a Windows Forms Application that displays a custom class Record objects and sorts them by how long they've been in my SortableBindingList<Record> record_list. When I start my program, I have some "dummy" records loaded into this list already for the sake of testing.
The SortableBindingList<T> has been taken from here.
public partial class Form1 : Form
{
public SortableBindingList<Record> record_list = new SortableBindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
dataGridView.DataSource = record_list;
FillData(); //Temporary function to insert dummy data for demo.
dataGridView.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.cell_formatting);
this.Controls.Add(dataGridView);
this.dataGridView.RowHeadersVisible = false;
this.dataGridView.Sort(this.dataGridView.Columns["UserName"], ListSortDirection.Ascending);
start_timer();
}
Result before "new" data is added (note: this was alphabetized automatically, specifically entered into the list out of alphabetical order):
Result after data is added:
Finally, result after I click the "UserName" header:
So, must I force a sort every time my DataSource is updated? If that's the case, how do I call a sort in such a manner?
Thank you for your assistance in advance!
You need to apply sort when the list changes.
The SortableBindingList<T> needs some changes to keep the the list sorted when some changes made in list. Here is the full code with changes which I made.
Pay attention The OnListChanged method of BindingList will be called automatically after adding and removing items. But if you need to OnListChanged also runs after changing properties of items, you should implement INotifyPropertyChanged for your model class.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class SortableBindingList<T> : BindingList<T>
{
private bool isSortedValue;
ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;
public SortableBindingList() : base() { }
public SortableBindingList(IList<T> list) : base(list) { }
protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
Type interfaceType = prop.PropertyType.GetInterface("IComparable");
if (interfaceType == null && prop.PropertyType.IsValueType)
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType);
if (underlyingType != null)
{
interfaceType = underlyingType.GetInterface("IComparable");
}
}
if (interfaceType != null)
{
sortPropertyValue = prop;
sortDirectionValue = direction;
IEnumerable<T> query = base.Items;
if (direction == ListSortDirection.Ascending)
query = query.OrderBy(i => prop.GetValue(i));
else
query = query.OrderByDescending(i => prop.GetValue(i));
int newIndex = 0;
foreach (object item in query)
{
this.Items[newIndex] = (T)item;
newIndex++;
}
isSortedValue = true;
sorting = true;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
sorting = false;
}
else
{
throw new NotSupportedException("Cannot sort by " + prop.Name +
". This" + prop.PropertyType.ToString() +
" does not implement IComparable");
}
}
bool sorting = false;
protected override PropertyDescriptor SortPropertyCore
{
get { return sortPropertyValue; }
}
protected override ListSortDirection SortDirectionCore
{
get { return sortDirectionValue; }
}
protected override bool SupportsSortingCore
{
get { return true; }
}
protected override bool IsSortedCore
{
get { return isSortedValue; }
}
protected override void RemoveSortCore()
{
isSortedValue = false;
sortPropertyValue = null;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (!sorting && sortPropertyValue != null)
ApplySortCore(sortPropertyValue, sortDirectionValue);
else
base.OnListChanged(e);
}
}
Here is my code that i used to create my own custom combobox column, my combobox is kind of special that shows treeview inside (here is code page http://www.brad-smith.info/blog/projects/dropdown-controls).
the new combobox column works well except in one thing, if i select for example from the combobox and navigate to the cell beside (which is textbox cell) and then navigate to the same combobox column in the second row and select another item from the second combobox then all is fine, but if i navigate from one combobox to the one under it directly and select an item then the first combobox will select same value of the second combobox.
any help please?
public class DataGridViewTreeComboBoxColumn : DataGridViewComboBoxColumn
{
public DataGridViewTreeComboBoxColumn() : base()
{
base.CellTemplate = new TreeComboBoxCell();
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(TreeComboBoxCell)))
{
throw new InvalidCastException("Must be a CalendarCell");
}
base.CellTemplate = value;
}
}
}
public class TreeComboBoxCell : DataGridViewComboBoxCell
{
public TreeComboBoxCell()
: base()
{
}
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue,
dataGridViewCellStyle);
TreeComboBoxEditingControl ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
ctl.SetItems(Items);
if (Value != null)
ctl.SelectedNode = ctl.AllNodes.ToList().First(x => x.Tag != null && x.Tag.Equals(Value));
ctl.SelectedNodeChanged += Ctl_SelectedNodeChanged;
}
public override object Clone()
{
TreeComboBoxCell dataGridViewCell = base.Clone() as TreeComboBoxCell;
if (dataGridViewCell != null)
{
}
return dataGridViewCell;
}
private void Ctl_SelectedNodeChanged(object sender, EventArgs e)
{
if (((TreeComboBoxEditingControl)sender).SelectedNode != null)
Value = ((TreeComboBoxEditingControl)sender).SelectedNode.Tag;
}
public override Type EditType
{
get
{
// Return the type of the editing control that CalendarCell uses.
return typeof(TreeComboBoxEditingControl);
}
}
public override Type ValueType
{
get
{
// Return the type of the value that CalendarCell contains.
return typeof(Object);
}
}
public override object DefaultNewRowValue
{
get
{
// Use the current date and time as the default value.
return 0;
}
}
}
public class TreeComboBoxEditingControl : ComboTreeBox, IDataGridViewEditingControl
{
DataGridView dataGridView;
private bool valueChanged = false;
int rowIndex;
public TreeComboBoxEditingControl()
{
this.TabStop = false;
}
public void SetItems(DataGridViewComboBoxCell.ObjectCollection items)
{
if (Nodes != null && Nodes.Count > 0)
return;
Action<ComboTreeNodeCollection> addNodesHelper = nodes => {
foreach (IGrouping<object, TreeComboBoxItem> group in items.Cast<TreeComboBoxItem>().GroupBy(x => x.Group).ToList())
{
ComboTreeNode parent = nodes.Add(group.Key.ToString());
foreach (TreeComboBoxItem item in group)
{
parent.Nodes.Add(item.Display).Tag = item.Value;
}
}
};
Action<ComboTreeBox> addNodes = ctb => {
addNodesHelper(ctb.Nodes);
ctb.Sort();
};
addNodes(this);
}
// Implements the IDataGridViewEditingControl.EditingControlFormattedValue
// property.
public object EditingControlFormattedValue
{
get
{
return GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting);
}
set
{
}
}
// Implements the
// IDataGridViewEditingControl.GetEditingControlFormattedValue method.
public object GetEditingControlFormattedValue(
DataGridViewDataErrorContexts context)
{
if (this.SelectedNode == null)
return null;
return this.SelectedNode.Tag;
}
// Implements the
// IDataGridViewEditingControl.ApplyCellStyleToEditingControl method.
public void ApplyCellStyleToEditingControl(
DataGridViewCellStyle dataGridViewCellStyle)
{
}
// Implements the IDataGridViewEditingControl.EditingControlRowIndex
// property.
public int EditingControlRowIndex
{
get
{
return rowIndex;
}
set
{
rowIndex = value;
}
}
// Implements the IDataGridViewEditingControl.EditingControlWantsInputKey
// method.
public bool EditingControlWantsInputKey(
Keys key, bool dataGridViewWantsInputKey)
{
// Let the DateTimePicker handle the keys listed.
switch (key & Keys.KeyCode)
{
case Keys.Left:
case Keys.Up:
case Keys.Down:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.PageDown:
case Keys.PageUp:
return true;
default:
return !dataGridViewWantsInputKey;
}
}
// Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit
// method.
public void PrepareEditingControlForEdit(bool selectAll)
{
// No preparation needs to be done.
}
// Implements the IDataGridViewEditingControl
// .RepositionEditingControlOnValueChange property.
public bool RepositionEditingControlOnValueChange
{
get
{
return false;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlDataGridView property.
public DataGridView EditingControlDataGridView
{
get
{
return dataGridView;
}
set
{
dataGridView = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlValueChanged property.
public bool EditingControlValueChanged
{
get
{
return valueChanged;
}
set
{
valueChanged = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingPanelCursor property.
public Cursor EditingPanelCursor
{
get
{
return base.Cursor;
}
}
protected override void OnSelectedNodeChanged(EventArgs e)
{
valueChanged = true;
this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
base.OnSelectedNodeChanged(e);
}
}
I think you should add the following to your TreeComboBoxCell class:
public override void DetachEditingControl()
{
var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
if (ctl != null)
ctl.SelectedNodeChanged -= Ctl_SelectedNodeChanged;
base.DetachEditingControl();
}
Also modify the following method to be:
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
ctl.SetItems(Items);
ctl.SelectedNode = initialFormattedValue != null ? ctl.AllNodes.FirstOrDefault(x => Equals(x.Tag, initialFormattedValue)) : null;
ctl.SelectedNodeChanged += Ctl_SelectedNodeChanged;
}
Also remove the ValueType override and let DefaultNewRowValue return null rather than 0.
But the main problem I see is the synchronization between your cell and editor classes. The cell class is using a TreeComboBoxItem objects inside Items collection, but editor GetEditingControlFormattedValue returns an object, which is actually TreeComboBoxItem.Value property. This way the base cell class canoot translate it correctly. I'm not sure you should even inherit from DataGridViewComboBoxCell because it expects a ComboBox editor in many of it's overrides that you don't handle. It might be better to inherit from DataGridViewCell or DataGridViewTextBoxCell like in the MSDN example your code is based on. At least you can try adding the following overrides to make your cell class implementation match your current editor implementation:
protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
{
if (value != null)
foreach (TreeComboBoxItem item in Items)
if (Equals(item.Value, value)) return item.Display;
return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
}
public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
{
return formattedValue;
}
EDIT Ok, I'm not sure what exactly doesn't work, it could be the usage. Here is a full working code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace System.Windows.Forms
{
public class TreeComboBoxItem
{
public object Group { get; set; }
public object Value { get; set; }
private string display;
public string Display { get { return display ?? (Value != null ? Value.ToString() : null); } set { display = value; } }
}
public class DataGridViewTreeComboBoxColumn : DataGridViewComboBoxColumn
{
public DataGridViewTreeComboBoxColumn()
{
base.CellTemplate = new TreeComboBoxCell();
}
public override DataGridViewCell CellTemplate
{
get { return base.CellTemplate; }
set { base.CellTemplate = (TreeComboBoxCell)value; }
}
}
public class TreeComboBoxCell : DataGridViewComboBoxCell
{
public TreeComboBoxCell() { }
public override Type EditType { get { return typeof(TreeComboBoxEditingControl); } }
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
ctl.SetItems(Items);
ctl.SelectedNode = Value != null ? ctl.AllNodes.FirstOrDefault(x => Equals(x.Tag, Value)) : null;
ctl.SelectedNodeChanged += OnEditorSelectedNodeChanged;
}
public override void DetachEditingControl()
{
var ctl = DataGridView.EditingControl as TreeComboBoxEditingControl;
if (ctl != null) ctl.SelectedNodeChanged -= OnEditorSelectedNodeChanged;
base.DetachEditingControl();
}
public override object Clone()
{
var dataGridViewCell = base.Clone() as TreeComboBoxCell;
if (dataGridViewCell != null)
{
}
return dataGridViewCell;
}
protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
{
if (value != null)
{
foreach (TreeComboBoxItem item in Items)
if (Equals(item.Value, value)) return (context & DataGridViewDataErrorContexts.Formatting) != 0 ? item.Display : value;
}
return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
}
public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
{
return formattedValue;
}
private void OnEditorSelectedNodeChanged(object sender, EventArgs e)
{
var selectedNode = ((TreeComboBoxEditingControl)sender).SelectedNode;
Value = selectedNode != null ? selectedNode.Tag : null;
}
}
public class TreeComboBoxEditingControl : ComboTreeBox, IDataGridViewEditingControl
{
public TreeComboBoxEditingControl() { TabStop = false; }
public DataGridView EditingControlDataGridView { get; set; }
public int EditingControlRowIndex { get; set; }
public bool EditingControlValueChanged { get; set; }
public bool RepositionEditingControlOnValueChange { get { return false; } }
public Cursor EditingPanelCursor { get { return Cursor; } }
public void SetItems(DataGridViewComboBoxCell.ObjectCollection items)
{
if (Nodes.Count > 0) return;
foreach (var group in items.Cast<TreeComboBoxItem>().GroupBy(x => x.Group))
{
var parent = Nodes.Add(group.Key.ToString());
foreach (var item in group)
parent.Nodes.Add(item.Display).Tag = item.Value;
}
Sort();
}
protected override void OnSelectedNodeChanged(EventArgs e)
{
EditingControlValueChanged = true;
EditingControlDataGridView.NotifyCurrentCellDirty(true);
base.OnSelectedNodeChanged(e);
}
public object EditingControlFormattedValue
{
get { return GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting); }
set
{
}
}
public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
{
if (SelectedNode == null) return null;
return (context & DataGridViewDataErrorContexts.Formatting) != 0 ? SelectedNode.Text : SelectedNode.Tag;
}
public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
{
BackColor = dataGridViewCellStyle.BackColor;
ForeColor = dataGridViewCellStyle.ForeColor;
}
public void PrepareEditingControlForEdit(bool selectAll)
{
}
public bool EditingControlWantsInputKey(Keys key, bool dataGridViewWantsInputKey)
{
switch (key & Keys.KeyCode)
{
case Keys.Left:
case Keys.Up:
case Keys.Down:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.PageDown:
case Keys.PageUp:
return true;
default:
return !dataGridViewWantsInputKey;
}
}
}
}
namespace Tests
{
class Parent
{
public string Name { get; set; }
public override string ToString() { return Name; }
}
class Child
{
public Parent Parent { get; set; }
public string Name { get; set; }
}
class TestForm : Form
{
public TestForm()
{
var parents = Enumerable.Range(1, 6).Select(i => new Parent { Name = "Parent " + i }).ToList();
var childen = Enumerable.Range(1, 10).Select(i => new Child { Parent = parents[i % parents.Count], Name = "Child " + i }).ToList();
var items = parents.Select((parent, i) => new TreeComboBoxItem { Value = parent, Group = "Group " + ((i % 2) + 1) }).ToArray();
var dg = new DataGridView { Dock = DockStyle.Fill, Parent = this, AutoGenerateColumns = false };
var c1 = new DataGridViewTreeComboBoxColumn { DataPropertyName = "Parent", HeaderText = "Parent" };
c1.Items.AddRange(items);
var c2 = new DataGridViewTextBoxColumn { DataPropertyName = "Name", HeaderText = "Name" };
dg.Columns.AddRange(c1, c2);
dg.DataSource = new BindingList<Child>(childen);
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}
}
I saw this in an application and just wondering if this can be done in winforms using C#.
I have no experience in customizing PropertyGrid control and I would highly appreciate if you can kindly help me on the codes. Thanks in advance.
Sceenshot:
There are few steps to accomplish that.
1. Define enum for your values.
public enum TypeCodes
{
Baking,
Barbecuing,
Blanching,
Blind_Baking,
Boiling,
Broiling,
Deep_Frying,
}
2. Create class with property of type List.
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
namespace PropertyGrid_ListBox_Test
{
class TestCase
{
public TestCase()
{
Manufacturer = new List<TypeCodes>();
}
[DisplayName("Manufacturer Preparation Type Code")]
public List<TypeCodes> Manufacturer { get; set; }
}
}
3. Create UserControl - TypeCodeSelectionControl which will be used to edit this property.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace PropertyGrid_ListBox_Test
{
public partial class TypeCodeSelectionControl : UserControl
{
public List<TypeCodes> List { get; set; }
private IWindowsFormsEditorService editorService;
public TypeCodeSelectionControl()
{
InitializeComponent();
}
private void PrepareListBoxes()
{
foreach (string myType in Enum.GetNames(typeof(TypeCodes)))
listBox1.Items.Add(myType);
foreach (TypeCodes type_code in this.List)
{
string enum_value = Enum.GetName(typeof(TypeCodes), type_code);
listBox1.Items.Remove(enum_value);
listBox2.Items.Add(enum_value);
}
}
public TypeCodeSelectionControl(List<TypeCodes> list, IWindowsFormsEditorService editorService)
{
InitializeComponent();
this.List = list;
this.editorService = editorService;
PrepareListBoxes();
}
private void buttonALL_Click(object sender, EventArgs e)
{
//
// Add all items.
//
foreach (object obj in listBox1.Items)
{
listBox2.Items.Add(obj);
}
listBox1.Items.Clear();
}
private void buttonAdd_Click(object sender, EventArgs e)
{
//
// Add selected item.
//
string item_to_add = (string)listBox1.SelectedItem;
listBox1.Items.Remove(item_to_add);
listBox2.Items.Add(item_to_add);
}
private void buttonRemove_Click(object sender, EventArgs e)
{
//
// Remove selected item.
//
string item_to_remove = (string)listBox2.SelectedItem;
listBox2.Items.Remove(item_to_remove);
listBox1.Items.Add(item_to_remove);
}
private void buttonNone_Click(object sender, EventArgs e)
{
//
// Remove all items.
//
foreach (object obj in listBox2.Items)
{
listBox1.Items.Add(obj);
}
listBox2.Items.Clear();
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
//
// Enable "Add" button only if any of items is selected in the left listbox.
//
buttonAdd.Enabled = listBox1.SelectedIndex != -1;
}
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
//
// Enable "Remove" button only if any of items is selected in the right listbox.
//
buttonRemove.Enabled = listBox2.SelectedIndex != -1;
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
//
// This occures when dropdown UI editor is hiding.
//
if (this.Visible == false)
{
//
// Clear previously selected items.
//
if (this.List == null)
{
this.List = new List<TypeCodes>();
}
else
{
this.List.Clear();
}
//
// Fill the list with currently selected items.
//
foreach (string obj in listBox2.Items)
{
this.List.Add((TypeCodes)Enum.Parse(typeof(TypeCodes), obj));
}
editorService.CloseDropDown();
}
}
}
}
4. Create UITypeEditor - TypeCodeEditor which will handle previously created UserControl.
using System;
using System.Collections.Generic;
using System.Drawing.Design;
using System.Windows.Forms.Design;
namespace PropertyGrid_ListBox_Test
{
class TypeCodeEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService = null;
if (provider != null)
{
editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
}
if (editorService != null)
{
TypeCodeSelectionControl selectionControl = new TypeCodeSelectionControl((List<TypeCodes>)value, editorService);
editorService.DropDownControl(selectionControl);
value = selectionControl.List;
}
return value;
}
}
}
5. Create TypeConverter to display our property value correctly.
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace PropertyGrid_ListBox_Test
{
class TypeCodeTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
List<TypeCodes> list = new List<TypeCodes>();
string[] values = ((string)value).Split(new char[] { ';' });
foreach (string v in values)
{
list.Add((TypeCodes)Enum.Parse(typeof(TypeCodes), v));
}
return list;
}
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
List<TypeCodes> list = (List<TypeCodes>)value;
string result = "";
foreach (TypeCodes type_code in list)
{
result += Enum.GetName(typeof(TypeCodes), type_code) + "; ";
}
return result;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
6. Add appropriate attributes to the property so it will become editable with our drop-down custom editor.
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
namespace PropertyGrid_ListBox_Test
{
class TestCase
{
public TestCase()
{
Manufacturer = new List<TypeCodes>();
}
[DisplayName("Manufacturer Preparation Type Code")]
[Editor(typeof(TypeCodeEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(TypeCodeTypeConverter))]
public List<TypeCodes> Manufacturer { get; set; }
}
}
7. Enjoy the results :).
I hope that piece of code will help you continue with writing your own custom editors.
Full example is available here: https://github.com/virious/PropertyGrid_CustomDropDownEditor.
I am quite new to WPF (from Winforms). I am using .Net 4.5 and the default DataGrid that comes along with the framework in WPF. The columns are created dynamically because I do not know at compile time. Now, based on data some columns will be read-only and some will be of ComboBox type.
How can I apply this logic dynamically while creating the columns dynamically as shown below. here is the code which I wrote so far. Whenever the data changes, the columns are generated dynamically based on the data.
Also, how do I generate "different types" of column dynamically (ComboBox, TextBox, etc...) based on data. The MVVM-ish way in WPF is kind of restricting me because I do not have much knowledge about templating. I am sure it should be easy once I get through.
NB: Currently all this is working fine. I have a read-only databound grid. But, there is no support for selective editable columns and selective ComboBox columns.
public class DatagridExtension {
public static readonly DependencyProperty RefDataSourceProperty =
DependencyProperty.RegisterAttached(
"RefDataSource",
typeof(RefDataRecord),
typeof(DatagridExtension),
new PropertyMetadata( default(RefDataRecord), OnRefDataSourceChanged)
);
private static void OnRefDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
var dataSource = e.NewValue as RefDataRecord;
grid.ItemsSource = dataSource;
grid.Columns.Clear();
int count = 0;
foreach (var col in dataSource.Columns)
{
grid.Columns.Add(
new DataGridTextColumn
{
Header = col.Name,
Binding = new Binding(string.Format("[{0}]", count))
}
);
count++;
}
}
public static RefDataRecord GetRefDataSource(DependencyObject dependencyObject)
{
return (RefDataRecord) dependencyObject.GetValue(RefDataSourceProperty);
}
public static void SetRefDataSource(DependencyObject dependencyObject, RefDataRecord value)
{
dependencyObject.SetValue(RefDataSourceProperty, value);
}
}
http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn.celltemplate(v=vs.95).aspx
WPF DataGrid creates DataGridComboBoxColumn by default if data source property type derives from Enum and sets DataGridColumn.IsReadyOnly by default if property doesn't have public setter or if property has ReadOnlyAttribute with ReadOnlyAttribute.IsReadOnly = true.
I will now show how to customize DataGrid column generation if your data source properties do not satisfy default conditions stated above.
Firstly, I will introduce two attributes used to specify that property is read-only (EditableAttribute) and that property should be visualized as ComboBox with predefined drop-down items (NameValueAttribute).
Here is EditableAttribute.cs:
using System;
namespace WpfApplication
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class EditableAttribute : Attribute
{
public bool AllowEdit { get; set; }
}
}
Here is NameValueAttribute.cs:
using System;
namespace WpfApplication
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public sealed class NameValueAttribute : Attribute
{
public string Name { get; set; }
public object Value { get; set; }
}
}
Next, we need some sample classes that will be used for demonstration.
So here is Person.cs class that will represent a single item (row) in a DataGrid:
using System.ComponentModel;
namespace WpfApplication
{
public class Person : ObservableObject
{
private string name;
private string surname;
private char gender;
public string Name
{
get { return this.name; }
set { this.SetValue(ref this.name, value, "Name"); }
}
[Editable(AllowEdit = false)]
public string Surname
{
get { return this.surname; }
set { this.SetValue(ref this.surname, value, "Surname"); }
}
[NameValue(Name = "Male", Value = 'M')]
[NameValue(Name = "Female", Value = 'F')]
public char Gender
{
get { return this.gender; }
set { this.SetValue(ref this.gender, value, "Gender"); }
}
}
}
Notice how Surname property has EditableAttribute applied and Gender property has NameValueAttributes applied.
And here is People.cs class that will represent DataGrid's data source:
using System.Collections.ObjectModel;
namespace WpfApplication
{
public class People : ObservableCollection<Person>
{
public People()
{
for (int i = 0; i < 100; ++i)
this.Items.Add(new Person()
{
Name = "Name " + i,
Surname = "Surname " + i,
Gender = i % 2 == 0 ? 'M' : 'F'
});
}
}
}
Base class for Person is ObservableObject.cs which is common to all data-binding applications:
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication
{
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void SetValue<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
}
Now, here is a XAML for MainWindow.xaml that hosts DataGrid control:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<Window.Resources>
<local:People x:Key="itemsSource"/>
</Window.Resources>
<DataGrid ItemsSource="{StaticResource itemsSource}" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
</Window>
Crucial part is DataGrid.AutoGeneratingColumn event handler OnAutoGeneratingColumn.
This event gets fired after DataGrid generates a DataGridColumn and is fired once for every auto-generated column. It is used to customize the auto-generated column or specify different one, depending on the provided data source property.
Here is MainWindow.xaml.cs code-behind in which OnAutoGeneratingColumn event handler does exactly that. It customized generated column by setting it as read-only if data source property has EditableAttribute with AllowEdit = false, and it overrides auto-generated column with DataGridComboBoxColumn if data source property has NameValueAttributes:
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var propertyDescriptor = (PropertyDescriptor)e.PropertyDescriptor;
var dataBoundColumn = (DataGridBoundColumn)e.Column;
var comboBoxColumn = GenerateComboBoxColumn(propertyDescriptor, dataBoundColumn);
if (comboBoxColumn != null)
e.Column = comboBoxColumn;
if (IsReadOnlyProperty(propertyDescriptor))
e.Column.IsReadOnly = true;
}
private static DataGridComboBoxColumn GenerateComboBoxColumn(PropertyDescriptor propertyDescriptor, DataGridBoundColumn dataBoundColumn)
{
var nameValueAttributes = Attribute.GetCustomAttributes(propertyDescriptor.ComponentType.GetProperty(propertyDescriptor.Name)).OfType<NameValueAttribute>().ToArray();
if (nameValueAttributes.Length > 0)
return new DataGridComboBoxColumn()
{
ItemsSource = nameValueAttributes,
DisplayMemberPath = "Name",
SelectedValuePath = "Value",
SelectedValueBinding = dataBoundColumn.Binding
};
else
return null;
}
private static bool IsReadOnlyProperty(PropertyDescriptor propertyDescriptor)
{
var editableAttribute = propertyDescriptor.Attributes.OfType<EditableAttribute>().FirstOrDefault();
return editableAttribute != null ? !editableAttribute.AllowEdit : false;
}
}
}
UPDATE FOR DYNAMIC CASE:
WPF supports dynamic reflection with ICustomTypeDescriptor implemented on data items and ITypedList implemented on collection.
Also, .NET 4.5 supports ICustomTypeProvider, but since I do not have .NET 4.5 installed, I haven't tested it.
NameValueAttribute.cs is same as before.
Here is very simple implementation of ICustomTypeDescriptor and ITypedList in a working sample:
DataProperty.cs
using System;
using System.ComponentModel;
namespace WpfApplication
{
public class DataProperty : PropertyDescriptor
{
private readonly Type propertyType;
private readonly bool isReadOnly;
private readonly Attribute[] attributes;
public DataProperty(string propertyName, Type propertyType, bool isReadOnly, params Attribute[] attributes)
: base(propertyName, null)
{
this.propertyType = propertyType;
this.isReadOnly = isReadOnly;
this.attributes = attributes;
}
protected override Attribute[] AttributeArray
{
get { return this.attributes; }
set { throw new NotImplementedException(); }
}
public override Type ComponentType
{
get { return typeof(DataRecord); }
}
public override Type PropertyType
{
get { return this.propertyType; }
}
public override bool IsReadOnly
{
get { return this.isReadOnly; }
}
public override object GetValue(object component)
{
return ((DataRecord)component)[this.Name];
}
public override void SetValue(object component, object value)
{
if (!this.isReadOnly)
((DataRecord)component)[this.Name] = value;
}
#region Not implemented PropertyDescriptor Members
public override bool CanResetValue(object component)
{
throw new NotImplementedException();
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
#endregion
}
}
DataRecord.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication
{
public class DataRecord : INotifyPropertyChanged, ICustomTypeDescriptor
{
public event PropertyChangedEventHandler PropertyChanged;
internal ITypedList container;
private readonly IDictionary<string, object> values = new SortedList<string, object>();
public object this[string propertyName]
{
get
{
object value;
this.values.TryGetValue(propertyName, out value);
return value;
}
set
{
if (!object.Equals(this[propertyName], value))
{
this.values[propertyName] = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return this.container.GetItemProperties(null);
}
#region Not implemented ICustomTypeDescriptor Members
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
throw new NotImplementedException();
}
string ICustomTypeDescriptor.GetClassName()
{
throw new NotImplementedException();
}
string ICustomTypeDescriptor.GetComponentName()
{
throw new NotImplementedException();
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
throw new NotImplementedException();
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
throw new NotImplementedException();
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
throw new NotImplementedException();
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
throw new NotImplementedException();
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
throw new NotImplementedException();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
throw new NotImplementedException();
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
throw new NotImplementedException();
}
#endregion
}
}
DataRecordCollection.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication
{
public class DataRecordCollection<T> : ObservableCollection<T>, ITypedList where T : DataRecord
{
private readonly PropertyDescriptorCollection properties;
public DataRecordCollection(params DataProperty[] properties)
{
this.properties = new PropertyDescriptorCollection(properties);
}
protected override void InsertItem(int index, T item)
{
item.container = this;
base.InsertItem(index, item);
}
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return this.properties;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
throw new NotImplementedException();
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<DataGrid x:Name="dataGrid" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
</Window>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var records = new DataRecordCollection<DataRecord>(
new DataProperty("Name", typeof(string), false),
new DataProperty("Surname", typeof(string), true),
new DataProperty("Gender", typeof(char), false, new NameValueAttribute() { Name = "Male", Value = 'M' }, new NameValueAttribute() { Name = "Female", Value = 'F' }));
for (int i = 0; i < 100; ++i)
{
var record = new DataRecord();
record["Name"] = "Name " + i;
record["Surname"] = "Surname " + i;
record["Gender"] = i % 2 == 0 ? 'M' : 'F';
records.Add(record);
}
this.dataGrid.ItemsSource = records;
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.Header = ((PropertyDescriptor)e.PropertyDescriptor).DisplayName;
var propertyDescriptor = (PropertyDescriptor)e.PropertyDescriptor;
var dataBoundColumn = (DataGridBoundColumn)e.Column;
var comboBoxColumn = GenerateComboBoxColumn(propertyDescriptor, dataBoundColumn);
if (comboBoxColumn != null)
e.Column = comboBoxColumn;
}
private static DataGridComboBoxColumn GenerateComboBoxColumn(PropertyDescriptor propertyDescriptor, DataGridBoundColumn dataBoundColumn)
{
var nameValueAttributes = propertyDescriptor.Attributes.OfType<NameValueAttribute>().ToArray();
if (nameValueAttributes.Length > 0)
return new DataGridComboBoxColumn()
{
ItemsSource = nameValueAttributes,
DisplayMemberPath = "Name",
SelectedValuePath = "Value",
SelectedValueBinding = dataBoundColumn.Binding
};
else
return null;
}
}
}
Firstly, one of the main advantages of WPF to WinForms is ability to declare user interface using templates. And you should avoid declaring UI components in code as as possible.
As i understand you want to display collection of different objects based on object type/data.
The best way to implement such logic - implement your own TemplateSelector
I suggest you read next articles:
http://www.wpftutorial.net/DataGrid.html
http://www.switchonthecode.com/tutorials/wpf-tutorial-how-to-use-a-datatemplateselector
P.S.
For reference. Example of declaring DataTemplate in code:
//create the data template
DataTemplate cardLayout = new DataTemplate();
cardLayout.DataType = typeof(CreditCardPayment);
//set up the stack panel
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
spFactory.Name = "myComboFactory";
spFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
//set up the card holder textblock
FrameworkElementFactory cardHolder = new FrameworkElementFactory(typeof(TextBlock));
cardHolder.SetBinding(TextBlock.TextProperty, new Binding("BillToName"));
cardHolder.SetValue(TextBlock.ToolTipProperty, "Card Holder Name");
spFactory.AppendChild(cardHolder);
//set up the card number textblock
FrameworkElementFactory cardNumber = new FrameworkElementFactory(typeof(TextBlock));
cardNumber.SetBinding(TextBlock.TextProperty, new Binding("SafeNumber"));
cardNumber.SetValue(TextBlock.ToolTipProperty, "Credit Card Number");
spFactory.AppendChild(cardNumber);
//set up the notes textblock
FrameworkElementFactory notes = new FrameworkElementFactory(typeof(TextBlock));
notes.SetBinding(TextBlock.TextProperty, new Binding("Notes"));
notes.SetValue(TextBlock.ToolTipProperty, "Notes");
spFactory.AppendChild(notes);
//set the visual tree of the data template
cardLayout.VisualTree = spFactory;
//set the item template to be our shiny new data template
drpCreditCardNumberWpf.ItemTemplate = cardLayout;
but as i say above, you should avoid this.
This is the correct answer - http://www.paulstovell.com/dynamic-datagrid (see the template creation logic dynamically. Its clever).
And, MMVM will be achieved like this - http://www.codeproject.com/Articles/36462/Binding-a-ListView-to-a-Data-Matrix (almost what I have posted in the question)
I was away from the Internet for a few days, but I think that I have found the better approach with simplified PropertyDescriptor architecture which doesn't require to implement ICustomTypeDescriptor. Here is the entire code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var records = new RecordCollection(new Property("Name"), new Property("Surname"));
for (int i = 0; i < 1000; ++i)
records.Add(new Record()
{
{ "Name", "John " + i },
{ "Surname", "Doe " + i }
});
this.dataGrid.ItemsSource = records;
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var property = e.PropertyDescriptor as Property;
if (property != null)
{
var binding = new Binding() { Path = new PropertyPath(property), Mode = property.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay };
var dataGridBoundColumn = e.Column as DataGridBoundColumn;
if (dataGridBoundColumn != null)
dataGridBoundColumn.Binding = binding;
else
{
var dataGridComboBoxColumn = e.Column as DataGridComboBoxColumn;
if (dataGridComboBoxColumn != null)
dataGridComboBoxColumn.SelectedItemBinding = binding;
}
}
}
}
public sealed class Record : INotifyPropertyChanged, IEnumerable
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly IDictionary<string, object> values = new SortedList<string, object>(StringComparer.Ordinal);
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
public object GetValue(string name)
{
object value;
return this.values.TryGetValue(name, out value) ? value : null;
}
public void SetValue(string name, object value)
{
if (!object.Equals(this.GetValue(name), value))
{
this.values[name] = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
public void Add(string name, object value)
{
this.values[name] = value;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.values.GetEnumerator();
}
}
public sealed class Property : PropertyDescriptor
{
private readonly Type propertyType;
private readonly bool isReadOnly;
public Property(string name)
: this(name, typeof(string))
{
}
public Property(string name, Type propertyType)
: this(name, propertyType, false)
{
}
public Property(string name, Type propertyType, bool isReadOnly, params Attribute[] attributes)
: base(name, attributes)
{
this.propertyType = propertyType;
this.isReadOnly = isReadOnly;
}
public override Type ComponentType
{
get { return typeof(Record); }
}
public override Type PropertyType
{
get { return this.propertyType; }
}
public override bool IsReadOnly
{
get { return this.isReadOnly; }
}
public override object GetValue(object component)
{
var record = component as Record;
return record != null ? record.GetValue(this.Name) : null;
}
public override void SetValue(object component, object value)
{
var record = component as Record;
if (record != null)
record.SetValue(this.Name, value);
}
public override bool CanResetValue(object component)
{
throw new NotSupportedException();
}
public override void ResetValue(object component)
{
throw new NotSupportedException();
}
public override bool ShouldSerializeValue(object component)
{
throw new NotSupportedException();
}
}
public sealed class RecordCollection : ObservableCollection<Record>, ITypedList
{
private readonly PropertyDescriptorCollection properties;
public RecordCollection(params Property[] properties)
{
this.properties = new PropertyDescriptorCollection(properties);
}
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return this.properties;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return string.Empty;
}
}
}
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<DataGrid x:Name="dataGrid" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
</Window>
The key thing in this code is creating a Binding with a BindingPath that contains a Property instance, instead of a string. This enables a simplification of PropertyDescriptor architecture because ICustomTypeDescriptor is not required anymore.
What do you think about this solution?