Binding to property of a class derived from CollectionBase - c#

I am trying to bind some controls to an object - which is normally a pretty straightforward process. Unfortunately, if the object that I'm binding to inherits from CollectionBase, binding to that classes fields causes the error:
Cannot bind to the property or column Caption on the DataSource. Parameter name: dataMember
Removing the collectionbase inheiritance makes this issue go away, but I need this object to be a collection. It seems as though CollectionBase causes higher level properties to become "unbindable." Is there some property I can override to fix this? Any other ideas?
I found this example online that summarized the issue pretty easily. Unfortunately, I have yet to find an answer in all the places I've seen this example posted.
Code:
[STAThread]
static void Main()
{
TestCollection obj = new TestCollection();
using (Form f = new Form())
using (BindingSource bs = new BindingSource())
{
bs.DataSource = typeof(Test);
f.DataBindings.Add("Text", bs, "Caption");
bs.DataSource = obj; // breaks
//List<TestallData = new List<Test>();
//allData.Add(obj);
//bs.DataSource = allData;
f.ShowDialog();
}
}
class TestCollection : CollectionBase
{
public string Caption { get { return "Working"; } }
}

CollectionBase provides interfaces for a List of Objects, as such when used as a datasource the binding tries to look inside the list for the individual binding data. When there is no list, you have a problem.
If you want a the caption and you want to use CollectionBase you should have 2 classes involved, not just one.
public class TestObj
{
public string caption { get { return "yay"; } }
}
public class TestCol : CollectionBase
{
//methods that implement CollectionBase for the TestObj type
}
with those two you can bind one of two ways.
TestObj obj = new TestObj();
TestCol col = new TestCol();
col.Add(obj);
//bind to obj, OR bind to col. Both would work with this setup.
http://msdn.microsoft.com/en-us/library/system.collections.collectionbase%28v=vs.90%29.aspx
There is a sample implementation of CollectionBase there.
UPDATE: EDITED FROM COMMENT
There isn't any method that I personally know which allows you to bind to the outer properties of a collection. As a workaround, you can use a 3 class system (yea, I know, more and more complicated).
public class TestHeader
{
public string Data {get;set;}
}
public class TestCol : CollectionBase
{
//...
}
public class TestObj
{
public TestHeader header {get;set;}
public TestCol col {get;set;}
}
bind the outer fields to TestObj.header and bind the collection fields to TestObj.col. This is a workaround, but as stated I dont actually know a way to directly implement what you seem to want. I wish I did, There are portions of my own code that would benefit from it.
Another Example
You could also do it with two classes, but you would still need to nest the collection itself
public class TestObj
{
public string data {get;set;}
public TestCol col {get;set;}
}
In this case, bind single data fields to TestObj, and collection fields to TestObj.col

Related

Read only list of lists c#

In general terms, a program I'm making involves storing a small number of entries (probably less than 30 at any given time) which can be categorized. I want to allow these entries to be seen but not altered from outside the class using them. I made a class called Entry which could be modified and another called ReadOnlyEntry which is a wrapper for an Entry object. The easiest way to organize these Entry objects it seems is to create a List<List<Entry>>, where each List<Entry> is a category. But then exposing that data in a readonly way became messy and complicated. I realized I would have to have one object of each of the following types:
List<List<Entry>> data;
List<List<ReadOnlyEntry>> // Where each ReadOnlyEntry is a wrapper for the Entry in the same list and at the same index as its Entry object.
List<IReadOnlyCollection<ReadOnlyEntry>> // Where each IReadOnlyCollection is a wrapper for the List<ReadOnlyEntry> at the same index in data.
IReadOnlyCollection<IReadOnlyCollection<ReadOnlyList>> readOnlyList // Which is a wrapper for the first item I listed.
The last item in the list would be exposed as public. The first lets me change entries, the second lets me add or delete entries, and the third lets me add or delete categories. I would have to keep these wrappers accurate whenever the data changes. This seems convoluted to me, so I'm wondering if there's a blatantly better way to handle this.
Edit 1:
To clarify, I know how to use List.asReadOnly(), and the stuff I proposed doing above will solve my problem. I'm just interested in hearing a better solution. Let me give you some code.
class Database
{
// Everything I described above takes place here.
// The data will be readable by this property:
public IReadOnlyCollection<IReadOnlyCollection<ReadOnlyList>> Data
{
get
{
return readOnlyList;
}
}
// These methods will be used to modify the data.
public void AddEntry(stuff);
public void DeleteEntry(index);
public void MoveEntry(to another category);
public void AddCategory(stuff);
public void DeleteCategory(index);
}
You can use List<T>.AsReadOnly() to return ReadOnlyCollection<T>.
Also, you're torturing the List<T> class storing the data the way you are. Build your own hierarchy of classes which store your individual lists.
.NET collections should support covariance, but they don't support it themselves (instead some interfaces support covariance https://msdn.microsoft.com/ru-ru/library/dd233059.aspx). Covariance means List<Conctrete> behaves like subclass of List<Base> if Concrete is subclass of Base. You can use interfaces covariation or just use casting like this:
using System.Collections.Generic;
namespace MyApp
{
interface IEntry
{
}
class Entry : IEntry
{
}
class Program
{
private List<List<Entry>> _matrix = null;
public List<List<IEntry>> MatrixWithROElements
{
get
{
return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
}
}
public IReadOnlyList<List<IEntry>> MatrixWithRONumberOfRows
{
get
{
return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
}
}
public List<IReadOnlyList<IEntry>> MatrixWithRONumberOfColumns
{
get
{
return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry) as IReadOnlyList<IEntry>);
}
}
public IReadOnlyList<IReadOnlyList<IEntry>> MatrixWithRONumberOfRowsAndColumns
{
get
{
return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
}
}
public void Main(string[] args)
{
}
}
}
Thanks to Matthew Watson for pointing on errors in my previous answer version.
You could make an interface for Entry which contains only getters; you would expose elements via this interface to provide read-only access:
public interface IEntry
{
int Value { get; }
}
The writable implementation would be simply:
public sealed class Entry : IEntry
{
public int Value { get; set; }
}
Now you can take advantage of the fact that you can return a List<List<Entry>> as a IReadOnlyCollection<IReadOnlyCollection<IEntry>> without having to do any extra work:
public sealed class Database
{
private readonly List<List<Entry>> _list = new List<List<Entry>>();
public Database()
{
// Create your list of lists.
List<Entry> innerList = new List<Entry>
{
new Entry {Value = 1},
new Entry {Value = 2}
};
_list.Add(innerList);
}
public IReadOnlyCollection<IReadOnlyCollection<IEntry>> Data => _list;
}
Note how simple the implementation of the Data property is.
If you need to add new properties to IEntry you would also have to add them to Entry, but you wouldn't need to change the Database class.
If you're using C#5 or earlier, Data would look like this:
public IReadOnlyCollection<IReadOnlyCollection<IEntry>> Data
{
get { return _list; }
}

Accessing Field from derived Class

I'm trying to access a field from a derived class in an array that holds references to the base class.
I have three classes:
abstract GameObjectBase
{
}
And derived from that are:
public Gamespace: GameObjectBase
{
private bool containsItem;
}
And:
public GameWall: GameObjectBase
{
}
(Obviously these classes hold more data, methods, and constructors).
I have created an array from these objects, like this
private GameObjectBase[,] _labyrinthArray = new GameObjectBase[10,10];
I then fill said array with Gamespaces and Gamewalls. But when I access a Gamespace object in the array, the containsItem field is not accessible due to the reference to the object being of type GameObjectBase.
Obviously I could put containsItem in GameObjectBase and make it accessible from there, but that doesn't fit my OOP approach. The only other solution I have found is to cast the object in question explicitely to Gamespace.
That seems quite crude and error prone to me. Is there any better solution to this?
First of all, you cannot reference a private field from outside the object class itself. You probably want to use a read-only property to encapsulate the field. If you don't want to cast the object explicitly to a Gamespace, you could use an interface instead.
public interface ICanContainItem
{
bool ContainsItem { get; }
}
public class Gamespace : GameObjectBase, ICanContainItem
{
private bool _containsItem;
public bool ContainsItem
{
get { return _containsItem; }
private set { _containsItem = value; }
}
}
This way you can then check whether the object "can contain an item" or not through the interface. Even if in the future you add new types of spaces that can contain an item, this same piece of code works, if the new types also implement the same interface.
var gameObject = _labyrinthArray[i,j]; //i,j defined elsewhere
var mayContainItem = gameObject as ICanContainItem;
if (mayContainItem != null)
{
var itemExists = mayContainItem.ContainsItem;
//mayContainItem.ContainsItem = false; //<-- fails because there's no setter
}

Make ComboBox accept only specific type

I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();

Get the DisplayMember value of any object in ListBox in WPF

I have a ListBox, and it's items consist of custom class objects (can be any class).
Then I set the DisplayMemberPath so the ListBox shows the right property of that custom class fine.
Now I need to enumerate the Items list of ListBox, and get the DisplayMember value of each item in the list, without knowing the type of the class in the list. Is there any way to get this DisplayMember value without Reflection?
In WPF, you don't need to implement an interface, or a base class for a container control to read the value of a property. In an ideal world, it would make sense to declare a base class or interface and have all of your custom classes extend, or implement these, but the benefit of that is really to keep your data type safe.
For example, in WPF, this is perfectly legal and will work just the same:
public class RadioButtonData
{
public string Label { get; set; }
public bool IsSelected { get; set; }
}
public class CustomData
{
public string Label { get; set; }
public string Value { get; set; }
}
...
private ObservableCollection<object> objects = new ObservableCollection<object>();
public ObservableCollection<object> Objects
{
get { return objects; }
set { objects = value; NotifyPropertyChanged("Objects"); }
}
...
Objects.Add(new RadioButtonData() { Label = "Some Value" });
Objects.Add(new CustomData() { Label = "Another Value" });
...
<ListBox ItemsSource="{Binding Objects}" DisplayMemberPath="Label" />
So as long as your various classes have the same name of property, then they will all be displayed in the same way, like above. They don't even have to be of the same type... just as long as the name matches that used in the ListBox.DisplayMemberPath property.
UPDATE >>>
Ah sorry, I misunderstood your question. In the case that you want to access these property values in code, then you have four basic options:
Define an Interface with a particular property and make your custom classes implement it.
Declare a base class with a particular property and make your custom classes extend it.
Create a (potentially long) section of if else statements that checks the type of each object and then accesses the relevant property.
Use reflection.
In my personal opinion, I would recommend options 1 or 2 first, then 4 and lastly 3. I'm really not sure what you have against reflection, but it's really not that bad, or slow... I'd certainly prefer to use it rather than having an else if statement for every possible type used.

WPF: Best way to create bindings to unknown types in MVVM

I am looking for a way to display data in a DataGrid from types that are unknown at compile-time.
I have the following base class
public abstract class Entity
{
// Some implementation of methods ...
}
In run-time, I load a plug-in DLL and use reflection to get a list of all the types derived from Entity. For example:
public class A : Entity
{
public LocalAddress Address{ get; set; }
}
public class B : Entity
{
public Vendor Vendor { get; set; }
public string Name { get; set; }
}
Then I retreive a list of their instances from DB
public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example
Entities is the DataGrid's ItemsSource, But what's the best way I can bind the properties to the DataGrid?
Since the properties can be complex, I also need to be able to bind to a specific path, for example Address.HomeNum ...
Clarifications
I only need to show a one grid of a type's instances at a time. The complete scenario is this:
I get a list of types that derive from Entity from the plug-in DLL through reflection
I show their names in a List. (in this example that list will contain A and B
When the user clicks on a specific item, let's say A, I get a list of A instances from DB - so far so good.
I want to display that list of A's instances in a DataGrid.
When the user selects another item from the list (meaning another type, lets say B), I get a list of B's instances from DB and need to display those in the grid and so on ...
The plug-in DLL is a class library with no xamls (also my users are the ones making this plug-ins and I don't want them to have to write DataTemplates for their entities.
I also can't make predifned DataTemplates as I don't know the types I'll need to display until run-time. Each type can have different types and amount of properties. All I know in complie-time is that they all derived from Entity.
The grid should also be editable.
A DataGrid seems inappropriate in this case. If your list was bound to two separate entities, it would break badly.
A better option would potentially be to use some other ItemsControl and set up a DataTemplate for each type of Entity. This would allow you to build custom editors per entity, and have a "list" of them to edit.
If you know the entities will always be of a single type, I'd instead build the collection of that specific type, and bind to it.
Since you don't know the property names of the Entities beforehand, I think your best option is to keep your DataGrid in Xaml but move the defintion and the Bindings of its DataGridColumns to the code behind.
AddColumnsForProperty(PropertyInfo property, string parentPath = "")
{
var title = property.Name;
var path = parentPath + (parentPath=="" ? "" : ".") + property.Name;
if(property.PropertyType == typeof(string))
{
var column = new DataGridTextColumn();
column.Header = title;
column.Binding = new Binding(path);
dataGrid.Columns.Add(column);
}
else if(property.PropertyType == typeof(bool))
{
//use DataGridCheckBoxColumn and so on
}
else
{
//...
}
var properties = property.GetProperties();
foreach(var item in properties)
{
AddColumnsForProperty(item, path);
}
}
Now if you execute these you'll have your dataGrid columns filled. and by adding all instances of the desired type in an observable collection and bind it to ItemsSource of the DataGrid it should work. selectedItem should be an instance of one the classes derived from Entity. The listbox contains new A() and new B() (or any existing instances of A and B) so selectedItem can be used in the following statement.
var propertyList = selectedItem.GetType().GetProperties();
foreach (var property in propertyList)
AddColumnsForProperty(PropertyInfo property);
how to write DataGridColumnTemplate in code
Edit:
Member can't be used in this scenario because INotifyPropertyChanged should get involved, so I replaced members with properties.
I would use attributes to specify what exactly is bindable (including composite object):
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class EntityAttribute : Attribute
{
internal abstract IEnumerable<EntityColumn> GetColumns(object instance, PropertyInfo property);
}
This attribute supports plain properties as well as composite structures. You should simply inherit and implement the method.
EntityColumn represents single value. Simplified version can be implemented like this:
public class EntityColumn
{
private readonly Action<object> _setMethod;
private readonly Func<object> _getMethod;
public string Caption { get; private set; }
public object Value
{
get { return _getMethod(); }
set { _setMethod(value);}
}
internal EntityColumn(string caption, Action<object> setMethod, Func<object> getMethod)
{
_getMethod = getMethod;
_setMethod = setMethod;
Caption = caption;
}
}
Later you can create single DataTemplate for EntityColumn and use it for all properties for all possible entities. Entity Object will contain additional method to return all EntityColumn relevant to it:
public IList<EntityColumn> GetColumns()
{
var objectType = GetType();
var properties = objectType.GetProperties();
return properties.SelectMany(
p => p.GetCustomAttributes<EntityAttribute>().SelectMany(a => a.GetColumns(this, p))).ToList();
}
For collection of Entities you can introduce EntityCollection which will absorb column information and provide structure similar to DataSet.
This implementation gives you flexibility of dynamic structure and keeps almost everything strongly typed. You can even extend attributes and EntityColumn to support validation.
As of displaying object, you'd rather use ItemsControl or even self written control inherited from ItemsControl to take advantage of knowing about Entity and EntityCollection classes.

Categories