I am using a DataRepeater to show data from a business objects on the screen. I am using windows forms in C# to accomplish this. The datasource is not available at compile time so I want to bind the datasource at runtime.
Here is the simplified scenario. I'm using this business class:
public class Product
{
private double _price;
public double Price
{
get
{
return _price;
}
set
{
_price = value;
}
}
}
I have created a ProductDataSource with the VisualStudio interface and bound the price to a label. Now I filled the datasource of my repeater in code:
dataRepeater1.DataSource = _productDataAgent.GetProducts();
When I startup my application the prices are correctly filled in the labels. So far so good.
Now I want the price labels to be updated when the product is updated. The Visual Studio interface helps me, and let me choose a 'Data Source Update Mode'. So I choose "OnPropertyChanged".
Here comes the tricky part. How does the .NET runtime know that the price property is updated from the backend. So I modify my business class to implement INotifyPropertyChanged. Like this:
public class Product : INotifyPropertyChanged
{
private double _price;
public double Price
{
get
{
return _price;
}
set
{
_price = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The problem is this doesn't work. When I update a product it remeanes un-updated in the interface. When I debug and change the property, I see that the PropertyChanged event is null so no one is listening.
Delving a little deeper in to the problem I found the following on the System.Windows.Forms.Binding Constructor page on MSDN:
An event named PropertyNameChanged.
So I tried using a (custom) PriceChanged event, but that did not work.
Am I doing something wrong here? I am comming from using WPF, so maybe this works a little different in Windows Forms? Is this because I am binding at runtime?
Jep found the sollution. Apparently you cannot simply bind to a list of products. You will see the products initially, but they will not be updated when a property is changed. Instead you need to statically bind to a BindingSource. Just create an object datasource using the Visual Studio (in the data menu). Code like this is generated:
private System.Windows.Forms.BindingSource beursProductDisplayBindingSource;
this.beursProductDisplayBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.dataRepeater1.DataSource = this.beursProductDisplayBindingSource;
Now you can dynamically bind like this:
BindingSource productBinding = ((BindingSource)dataRepeater1.DataSource);
_productDataAgent.BeursProducts.ForEach(product => productBinding.Add(product));
Now when implementing INotifyPropertyChanged in your data object like I did is works like expected. Just forgot one step which is not needed when using WPF.
Related
I am using Xamarin and MVVMCross to implement an IOS app. A view I'm working on is displaying correctly, but ONLY when I hard-code the data being bound to inside my ViewModel and NOT when (as by design and necessity) my data arrives late from SQLite, only an indeterminate time after the view is shown.
What I am using and accomplished so far:
Working/Showing storyboard for my View that has a UICollectionView
inside (called: CollectionView in code below)
Custom layout and XIB file for every UICollectionViewCell that also displays correctly in my view
A view that works and displays correctly only if ViewModel data is fully populated the moment ViewDidLoad() is called.
Problem:
My data in my ViewModel is updated by the Model's databases in an uncertain amount of time whilst the view is happily being shown. When I bind the data as shown below (and trying two-way/one-way bindings and the like as well), I don't get updates on my view as the final data comes in later.
What I can't seem to do:
Redraw the UICollectionView or maybe refresh the
MvxCollectionViewSource below to ensure that as the ViewModel's data changes, I can actually redraw the UICollectionView and show my
custom cells with new and updated data.
THE CODE(TM)
The CollectionView cells are implemented as follows. I followed all examples online and from that Stuart Bloke and his Kittens to make sure I implement all the patterns exactly the same:
[Register("MyCell")]
public partial class MyCell : MvxCollectionViewCell
{
public static readonly UINib Nib = UINib.FromName("MyCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString("MyCell");
public MyCell(IntPtr handle) : base(handle)
{
this.DelayBind(() => {
var set = this.CreateBindingSet<MyCell, SomeModelClass>();
set.Bind(Label1).To(item => item.Label1);
set.Bind(Label2).To(item => item.Label2);
set.Apply();
});
}
public static MyCell Create()
{
return (MyCell)Nib.Instantiate(null, null)[0];
}
}
My ViewDidLoad() in the View looks something like this:
CollectionView.RegisterNibForCell(MyCell.Nib, MyCell.Key);
var source = new MvxCollectionViewSource(CollectionView, MyCell.Key);
CollectionView.Source = source;
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(source).To(vm => vm.ListOfStuff);
set.Apply();
CollectionView.ReloadData();
NB! The ListOfStuff shown above is really just a List of a custom class containing 2 strings right now.
TL:DR: I don't know ListOfStuff's values the moment I call the above code. When I hard-code them in the ViewModel, I get joy. If I don't, I don't, even as data gets updated correctly later.
I now reach out to you, the neurons of the brain of crowdsourcing...
Instead of using a List<T> use ObservableCollection<T> and new items should be added to the CollectionView.
The UI needs to know when the collection has changed. ObservableCollection<T> implements INotifyCollectionChanged and INotifyPropertyChanged and communicates with the UI when the collection changes.
You shouldn't need ReloadData() anymore if you're using ObservableCollection<T>.
This extension method might be of use when adding range of IEnumerable<T>
public static class ObservableCollectionExtensionMethod
{
public static void AddRange<TSource>(this ObservableCollection<TSource> source, IEnumerable<TSource> collection)
{
foreach (var i in collection) source.Add(i);
}
}
I'm trying to create my calendar control with databinding.
public partial class Calendar : UserControl
{
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(DateTime),
typeof(Calendar), null);
public object Date
{
get { return GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
OnPropertyChanged("Date");
}
}
public Calendar()
{
// Required to initialize variables
InitializeComponent();
DayText.Text = ((DateTime)Date).ToString("dd");
MonthText.Text = ((DateTime)Date).ToString("MMM");
this.Loaded += new RoutedEventHandler(Calendar_Loaded);
this.GotFocus += new RoutedEventHandler(Calendar_Loaded);
}
void Calendar_Loaded(object sender, RoutedEventArgs e)
{
DayText.Text = ((DateTime)Date).ToString("dd");
MonthText.Text = ((DateTime)Date).ToString("MMM");
}
}
But When I create the listbox with this control, same calndar have the wrong date. I'm sure that the Date passed thorough databinding is correct but I don't understand why same calender show a different day (I'm noticed that is the day of a previous calendar control intance)
Thank you for supporting!
Hmm ... where do we start? Here's a few things I've noticed:
If you're using a dependency property, there's no need to call OnPropertyChanged from the Date property setter.
The dependency property declares the type as DateTime, but your public exposed property is of type object, which then requires you to cast it elsewhere.
If Calendar_Loaded is to be called in more situations than in response to the Loaded event (such as the GotFocus event, then I'd recommend that you call it something else, or create a method with a relevant name (e.g. UpdateDateParts) and call it from properly named separate event handlers.
Using fixed format specifiers when processing date strings does not localize well.
In addition to that, I'd suggest that you could implement the user interface in a manner that supports databinding (and re-templating) by using bindings and exposing the date parts of the Date dependency property instead of manually updating the Text property of some text blocks/boxes in event handlers. In fact, if you derive from Control instead of UserControl then you can create and actuall lookless control that has it's user interface defined by a style in themes\generic.xaml that can be re-defined by users of your control.
As for why the date is incorrect in different instances of your calendar control, we'd need to see some of your XAML/code to see how the control is being used and initialized to be able to provide a better answer. However, I thought the above was worth putting in an Answer, instead of trying to say it in a Comment.
I thought this was a simple problem, but I can't find any information on the web. I'm binding a ListBox to a List using BindingSource like so:
List<Customer> customers = MyMethodReturningList();
BindingSource customersBindingSource = new BindingSource();
customersBindingSource.DataSource = customers;
customersListBox.DataSource = customersBindingSource;
Now, when I add or delete from customers list, my ListBox gets updated (even without using ResetBindings on BindingSource), but if I change any of the customer objects in the list, it does not. Calling ResetBindings has no effect. I even implemented my own BindingList, but the behaviour hasn't changed.
The Customer class uses properties for accessing and modification of data. Its ToString() content is displayed in the list.
I'm using C# in .Net 2.0.
Any ideas?
Thanks
If you use a BindingList you don't even need the BindingSource:
BindingList<Customer> customers = new BindingList<Customer>(MyMethodReturningList());
customersListBox.DataSource = customers;
OK, here's a dirty fix: wenever you need to refresh the box contents set datasource = null, then rebind it.
the reason it doesn't update is because the objects in the list haven't changed and it only checks the refrences of the object rather than their contents.
There is also a bug in the list box which can cause this problem. If you set the SelectionMode to None this problem appears.
As a work around I set the selection mode to One and then back to None when updating the datasource.
I got around this problem by converting data to array when updating source. Please see UpdateData method. This way you can update your combo box without losing ComboBox Settings.
class Person {
public int Id {get; set; }
public string FirstName{ get; set; }
public string SurName {get; set; }
}
public Form1()
{
InitializeComponent();
comboBox1.DisplayMember = "FirstName";
comboBox1.ValueMember = "Id";
comboBox1.DataSource = m_PersonList;
}
public void UpdateData() {
m_PersonList[0].FirstName = "Firstname1";
comboBox1.DataSource = m_PersonList.ToArray<Person>();
}
I understand that this question was asked almost 6 years ago but other than work-arounds I do not see a correct answer here.
When you change property of an item in a collection the event gets raised for the element (object) but not the collection. So the collection does not see a change and will not refresh bound controls. Elements inside all binding collections and most generic collections like List<> receive 2 events, PropertyChanging and PropertyChanged. When a property of the element inside collection is changed, the event gets triggered. All you need to do is add an event handler that would trigger either re-binding or raise an event on the Collection.
I'm not clear about this....
When having a gridview on the View, is the controller who has to set up the Data source, columns, etc? or I just have to expose the DataBinding stuff, fire it from the controller and let the html/codebehind on the view handle all the rendering and wiring up?
To be more precise: on the view should I have
private GridView _gv
public _IList<Poco> Source {
get {_gv.DataSource;}
set {_gv.DataSource = value;
_gv.DataBind();}
}
Or should it be (from MVP pattern - Passive View and exposing complex types through IView (Asp.Net, Web Forms))
private GridView _datasource;
public DataSource
{
get { return _datasource; }
set
{
_datasource = value;
_datasource.DataBind();
}
}
Maybe I'm having it all wrong ....
Where can I find an example that is not a "Hello world" example on MVP for ASP.Net???
Your controller should be in charge of setting the "result" of the databinding. The view is in charge of displaying it propertly.
So for example, your webform/usercontrol (View) could have the data source exposed as an object property that your View should know how to handle when it receives it:
public MyObject DataSource
{
set
{
_datasource = value;
_datasource.DataBind();
}
}
So if you need to have an ItemDataBound event, I would still handle it in the view. Even though there could be business logic in the event. If you need to have business logic in the event, I would put it in the MyObject result before it is passed to the view.
So an example would be to have a property of "MyObject" be "AllowDelete" and in your ItemDataBound, the value of this property determines if a column in the GridView is enabled or not.
Having just listened to a recent Hanselminutes on this topic, it might be worth having a look at the http://webformsmvp.com/ project, which seems to bring a bit of rigidity into separating concerns within WebForms.
I have a question regarding a data binding(of multiple properties) for custom DataGridViewColumn.
Here is a schema of what controls that I have, and I need to make it bindable with DataGridView datasource. Any ideas or a link to an article discussing the matter?
Controls
Graph Control(custom): Displayed in
the custrom DataGridView column. Has
properties like "Start Date",
"EndDate", Windows Chart control,
which is itself, bindable, etc.
Custom cell(DataGridViewCustomCell inherits
from DataGridViewCell) that holds
the Graph control and processes some
events(OnEnter event, for example,
passes the focus to the custom Graph
column for drag-n-drop type of
events, etc.)
Custom column(DataGridViewCustomColumn
inherits from DataGridViewColumn)
that defined the cell template type:
CellTemplate = new
DataGridViewCustomCell(); and also a
primary choice for data binding
Data Structure:
Main table to be displayed in other DataGridView Columns
Graph table - related to the Main table via parent-child relationship. Holds graph data
Chart table related to the graph table via parent-child relationship. Holds data for the win-form chart, which is a part of my Graph control.
So far I cannot even bind data from the Graph table to by Graph control or Graph-holding Column/Cell.
Thank you for your answer. My data sources is not a SQL data source, and as a matter of fact I was talking about datagridview for win-forms(I'm not sure that was clear).
As I did not get the reply on any of the forums I was asking the question, I figured, I would outline a solution I came up with, for those who may have a similar problem and for possible critique. :-)
(steps 1-2 are also explained in the famous MS example)
1. Create your own classes that inherit from DataGridViewColumn and DataGridViewCell, setup the column template;
2. Create your "CustomEdit" control
In the data item, whatever that is, a DataRow, or a List item, add a read-only property, that return the object itself. This property is bound to the custom column.
Custom Cell:
public partial class MyCell : DataGridViewCell
{
protected override void Paint(...)
{...} // draws control
// receives data item as a value
// in my case I have to custom-draw entire control in this fnc.
public override void InitializeEditingControl(...)
{...} // initialize control editing
// override some other properties
public override Type EditType {
get{
return typeof(MyEditControl);
}
}
public override Type ValueType{
get{
return typeof(MyItem);
}
}
}
Custom Column:
public partial class MyColumn : DataGridViewColumn
{
public MyColumn(){ ...
CellTemplate = new MyCell();
}
}
Edit Control:
public partial class MyEditControl : UserControl, IDataGridViewEditingControl
{... // implements IDataGridViewEditingControl
// value is our data item
}
Data Item, the data sources becomes List<MyItem>
public class MyItem:Object{
...
[XmlIgnore] // I need it because I do serialization
public MyItem Self {
get {
return this;
}
}
}
See my question Here
It's easy to do, you just don't use the IDE to do it, you do it all in code. It's a lot of work, but it's not that difficult if you know what your doing. I went from knowing nothing to being able to do it in less than a day so I'm sure you'll be able to do it.
Edit: you can also use a Join in the sql that populates the datagridview