Setup:
Class Model with one property which type is an interface.
Create an instance and add it to a winforms bindingsource
Add a textbox to winforms and configure the text value to use the bindingsource with the property as a datamember
Model
public class Model
{
public ICustomer Customer { get; set; }
}
public class Customer : ICustomer
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public interface ICustomer
{
string Name { get; set; }
}
Binding
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.bsModel, "Customer", true));
Problem
When I run this code, the textbox remains empty (instead of showing the name of the customer).
BUT it does show it when I change the type of the Customer property in Model to the concrete Customer type.
I can't find any reason on MSDN why this is? Any ideas?
(Preferably no workaround like storing the toString value into another property, there is a framework doing this binding where I prefer not to hack into)
Probably you should specify the property that should be binded to the Text property of the TextBox
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding
("Text", this.bsModel, "Customer.Name", true));
If you remove the interface ICustomer and use directly the concrete class Customer then the binding code uses the ever present ToString() method that you have overridden in the concrete class and thus you get your textbox set.
For example, try to change the ToString to return a Surname property
Set the formattingEnabled property to false fixes it so it uses the toString() method as in the concrete implementation.
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.bsModel, "Customer", false));
Problem:
I just browsed through the source at msdn and bumped into the following line:
if (e.Value != null && (e.Value.GetType().IsSubclassOf(type) || e.Value.GetType() == type || e.Value is System.DBNull))
return e.Value;
According to MSDN:
The IsSubclassOf method cannot be used to determine whether an interface derives from another interface, or whether a class implements an interface.
So this will evaluate to false and the further conversion will end up in returning null.
By setting formattingEnabled to false the parse method won't be called and the value will be simply returned instead.
Not sure if it's done on purpose or it's a bug. But I've got the feeling I better set the formattingEnabled to false for concrete types as well.
#Steve solution is working as well! (thanks)
But he's working around the interface type.
I just prefer simply working with toString(), since it can change over time and it's much easier to maintain.
Related
So, my question is about the exact methodology behind windows form data binding.
I wrote a simple code, where i created a View, an IViewModel interface and a ViewModel.
interface IVM
{
}
and
public class Vm : IVM
{
int number;
public int Number
{
get
{
return this.number;
}
set
{
this.number = value;
}
}
}
the form looks like:
public partial class Form1 : Form
{
private IVM vm;
public Form1()
{
InitializeComponent();
this.vm = new Vm();
this.iVMBindingSource.DataSource = this.vm;
}
}
and the related designer part is:
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.iVMBindingSource, "Number", true));
...
this.iVMBindingSource.DataSource = typeof(WindowsFormsApplication1.IVM);
You can clearly see that IViewModel interface does not publish a Number property, but the concrete ViewModel class has a Number property.
Although in design time i can't use the designer to bind the property (since IVM has no Number prop), i can manually write "iVMBindingSource - Number" into the textbox's Test property, to bind it.
My question is, how does binding work EXACTLY? Why don't I receive a runtime error, while trying to access IVM's not existing Number property?
(I tested and it actually changes the VM's Number prop properly)
Does it use some kind of reflection? How does this "magic" binding string works?
Thanks for your answers!
Jup it's done by reflection. I just checked the code and the binding is done by the Binding class. There is a method called CheckBindings which ensures the property you want to bind on is available. It basically works like this:
if (this.control != null && this.propertyName.Length > 0)
{
// ...certain checks...
// get PropertyDescriptorCollection (all properties)
for (int index = 0; index < descriptorCollection.Count; ++index)
{
// select the descriptor for the requested property
}
// validation
// setup binding
}
As Ike mentioned, you can find the source code here:
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Binding.cs,3fb776d540d0e8ac
MSDN Reference: https://msdn.microsoft.com/en-us/library/system.windows.forms.binding(v=vs.110).aspx
As derape already mentioned, Binding uses reflection. It must use reflection because it cannot know anything about the class you are using. The evaluation will be done at runtime. Since your concrete type Vm got the specified property Number, reflection will return it and Binding class is satisfied. Binding is really flexible as long as the property name is valid.
On the other hand, when you are using the designer, it cannot know which concrete type you will use. Therefore it only allows you to use properties of the common base IVM. If you enter the string manually, design time evaluation will be skipped and input is passed to the binding constructor.
If you want to use designer support, just use the the concrete type or if you don't know the concrete type but need the property Number, simply create a new interface and derive from IMV.
I have a problem that is difficult to explain. Essentially I have a list of a certain class we can call MyObj. One of the properties of this object is a custom list itself. I would like to bind this List to a dataGridView and have this particular property that is also a list show up. Any ideas? Am I being clear enough? :-P..
Here is the idea. I have my own custom list object overriding the ToString() method:
public class CategoriesList : List<Category>
{
public override string ToString()
{...}
}
This is used as a property in an object such as:
public MyObj
{
public string Property1 {get; set; }
public string Property2 {get; set; }
public CategoriesList Categories {get; set; }
}
In turn, I have a list of these objects such as:
List<MyObj> myDataSouce = SomeRepository.GetMyObjList();
Where I bind this to a datagrid view:
MyDataGridView.DataSource = myDataSource;
Property1 and Property2 are automatically generated. Is there any way to have the CategoriesList property be added as well? I previously thought Overriding the ToString() method on a class would be enough..
I am really lost on this one as I have no idea how to even google for it :-P
Assuming that you'd like to display a specific value in place of the list in the datagridview, you'll want to use a custom TypeConverter. Otherwise you'll need to place a control in the datagridview column that supports lists, like a drop down list and bind to that.
For the former:
Basically decorate your categories property with a custom typeconverter:
[TypeConverter(typeof(MyConverter))]
public CategoriesList Categories { get; set; }
Then use a custom type converter that basically tells the datagrid that when it encounters the categories property what do display:
public class MyConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is CategoriesList) {
return value.ToString();
}
return base.ConvertFrom(context, culture, value);
}
}
You'll need to add your column to be databound manually by adding an unbound column and specify the DataPropertyName for the property to be mapped to that column, in this case "Categories"
If you're looking to display second level properties as well then this may help:
http://blogs.msdn.com/b/msdnts/archive/2007/01/19/how-to-bind-a-datagridview-column-to-a-second-level-property-of-a-data-source.aspx
This might help... look at my answer there, I haven't tried it with a property that is also a type of list but I think the idea is the same.
Or this one as well, I also have an answer there with a sample code too...
I'm binding a GridView to a collection of objects that look like this:
public class Transaction
{
public string PersonName { get; set; }
public DateTime TransactionDate { get; set; }
public MoneyCollection TransactedMoney { get; set;}
}
MoneyCollection simply inherits from ObservableCollection<T>, and is a collection of MyMoney type object.
In my GridView, I just want to bind a column to the MoneyCollection's ToString() method. However, binding it directly to the TransactedMoney property makes every entry display the text "(Collection)", and the ToString() method is never called.
Note that I do not want to bind to the items in MoneyCollection, I want to bind directly to the property itself and just call ToString() on it.
I understand that it is binding to the collection's default view. So my question is - how can I make it bind to the collection in such a way that it calls the ToString() method on it?
This is my first WPF project, so I know this might be a bit noobish, but pointers would be very welcome.
You can add property StringRepresentation or something like this in MyMoney class. If you do not want to affect this class, you should write a wrapper - MyMoneyViewModel which will have all needed properties. This is a common way. HIH!
Write a IValueConverter implementation that calls ToString() on the bound collection and returns it and use this converter in the XAML binding expression.
In a simple form, I bind to a number of different objects -- some go in listboxes; some in textblocks.
A couple of these objects have collaborating objects upon which the ToString() method calls when doing its work -- typically a formatter of some kind.
When I step through the code I see that when the databinding is being set up,
ToString() is called
the collaborating object is not null and returns the expected result
when inspected in the debugger, the objects return the expected result from ToString()
BUT the text does not show up in the form.
The only common thread I see is that these use a collaborating object, whereas the other bindings that show up as expected simply work from properties and methods of the containing object.
If this is confusing, here is the gist in code:
public class ThisThingWorks
{
private SomeObject some_object;
public ThisThingWorks(SomeObject s) { some_object = s; }
public override string ToString() { return some_object.name; }
}
public class ThisDoesntWork
{
private Formatter formatter;
private SomeObject some_object;
public ThisDoesntWork(SomeObject o, Formatter f)
{
formatter = f;
some_object = o;
}
public override string ToString()
{
return formatter.Format(some_object.name);
}
}
Again, let me reiterate -- the ToString() method works in every other context -- but when I bind to the object in WPF and expect it to display the result of ToString(), I get nothing.
Update:
The issue seems to be what I see as a buggy behaviour in the TextBlock binding. If I bind the Text property to a property of the DataContext that is declared as an interface type, ToString() is never called. If I change the property declaration to an implementation of the interface, it works as expected. Other controls, like Label work fine when binding the Content property to a DataContext property declared as either the implementation or the interface.
Because this is so far removed from the title and content of this question, I've created a new question here: WPF binding behaviour different when bound property is declared as interface vs class type?
changed the title: WPF binding behaviour different when bound property is declared as interface vs class type?
Try these simple changes:
First test your program with this version of the method:
public override string ToString()
{
return "This method's really being called."
}
If that actually displays something in the UI, now try this version:
public override string ToString()
{
Console.WriteLine(
string.Format("some_object.name = {0}, formatter.Format(some_object.name) = {1}",
some_object.name,
formatter.Format(some_object.name));
return formatter.Format(some_object.name);
}
If this doesn't lead you to figure out what's really wrong, I'll be extremely surprised.
Imagine these two classes:
class Part
{
public string Name { get; set;}
public int Id { get; set; }
}
class MainClass
{
public Part APart { get; set;}
}
How can I bind MainClass to a combo box on a WinForm, so it displays Part.Name (DisplayMember = "Name";) and the selected item of the combo sets the APart property of the MainClass without the need to handle any events on the dropdown.
As far as I know, setting ValueMember of the ComboBox to "Id" means that it will try to set APart to a number (Id) which is not right.
Hope this is clear enough!
What you're looking for is to have the ValueMember (= ComboBox.SelectedItem) be a reference to the object itself, while DisplayMember is a single property of the item, correct? As far as I know, there's no good way to do this without creating your own ComboBox and doing the binding yourself, due to the way ValueMember and DisplayMember work.
But, here's a couple things you can try (assuming you have a collection of Parts somewhere):
Override the `ToString()` method of `Part` to return the `Name` property. Then set your `ComboBox`'s `ValueMember` to `"APart"` and leave `DisplayMember` null. (Untested, so no guarantees)
You can create a new property in Part to return a reference to itself. Set the 'ValueMember' to the new property and 'DisplayMember' to `"Name"`. It may feel like a bit of a hack, but it should work.
Do funny things with your `APart` getter and setter. You'll lose some strong-typing, but if you make `APart` an object and `MainClass` contains the collection of `Part`s, you can set it by `Id` (`int`) or `Part`. (Obviously you'll want to be setting it by Id when you bind the ComboBox to it.)
Part _APart;
object APart
{
get {return _APart;}
set {
if(value is int)
_APart = MyPartCollection.Where(p=>p.Id==value).Single();
else if(value is Part)
_APart = value;
else
throw new ArgumentException("Invalid type for APart");
}
}
If you set the ValueMember of the combo box to null, the databinding will return the selected item (i.e. the Part instance) instead of speciied member. Set the DisplayMember to 'Name'.
I made a little research and found this article where the author has been able to bind to nested properties by extending the standard binding source component.
I've tried it and it seems to work fine.
create a backing class to hold the "information", and create properties for all the data. Then implement System.ComponentModel.INotifyPropertyChanged on that class, something like:
private String _SelectedPart = String.Empty;
public String SelectedPart
{
get
{
return _SelectedPart;
}
set
{
if (_SelectedPart != value)
{
_SelectedPart = value;
// helper method for handing the INotifyPropertyChanged event
PropertyHasChanged();
}
}
}
Then create an "ObjectDataSource" for that class (Shift-Alt-D in VS2008 will bring that up while looking at a form), then click on your ComboBox and set the following properties:
DataSource, set to the ObjectDataSource "BindingSource" you just created.
DisplayMember, Set to the Name propertity of the List of parts
ValueMember, Set to the ID member of the List of parts
DataBindings.SelectedValue, set to the SelectedPart on the "BindingSource" you just created.
I know the above sounds complex, and it might take a bit to find all the parts I just described (wish I could give a tutorial or screenshot), but really it is VERY fast to do once you get used to it.
This is by the way, considered "data-binding" in .NET and there are a few good tutorials out there that can give your more information.