So I tried using both BindingList and BindingSource, but the issue is the same with both.
Just for some background: I have a application where I receive updates on trading objects from an api. I receive real time the updates from the api (Can be an add/update/remove updateType) and process them into a repository class that has lists each respective object type, which all inherit from a parent class call DSO(for DataSourceObject).
This Repository has an instance in another class called DataSource (I will reference this later.)
So I have my multiple lists in my Repository which sits in DataSource and all operations (add/remove/update), work fine on those lists up to that point.
Now, on my UI, I have a frmDashboard form, which calls a frmDataWindow form.
This frmDataWindow has a DataGridView where I want to show my various DSO childClass Objects (3 examples are DSOPorfolio, DSOInstrument, DSOTrade).
Here is where I am having issues, I have tried different methods, but I am currently using the following approach:
I declare a new instance of frmDataWindow, in a separate method I pass a reference of a DataSource (or what I believe is a reference, because my understanding is that c# passes everything by reference as a default) to the instance of frmDataWindow. This DataSource already has a Repository with loaded lists of my BusinessObjects.
To the frmDataWindow instance, I then pass the type of object via enum (let's call it DSOType) that I want binded to the DataGridView.
I then run a switch statement that assigns the List of DSO objects to a binding source, while converting it to the proper type of DSO child class (so all properties show on the DataGridView).
Just a note that I have already implemented INotifyPropertyChanged in all of my DSO objects.
DataSource _ds;
BindingSource bs;
public void AssignDataSource(DataSource ds)
{
_ds = ds;
}
public void AssignDSO(DSOType type)
{
try
{
_dsoType = type;
dgvMain.Rows.Clear();
dgvTotal.Rows.Clear();
switch (_dsoType)
{
case DSOType.Portfolio:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOPortfolio)x), null);
break;
}
case DSOType.Instrument:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOInstrument)x), null);
break;
}
case DSOType.Trade:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOTrade)x), null);
break;
}
case DSOType.ClosedTrade:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOClosedTrade)x), null);
break;
}
case DSOType.Order:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOOrder)x), null);
break;
}
case DSOType.Position:
{
bs = new BindingSource(_ds.DSORepository.GetDSOList(type).ConvertAll(x => (DSOPosition)x), null);
break;
}
default:
{
bs = null;
break;
}
}
string text = text = _ds.DSORepository.GetDSOList(type)[0].ObjectType.ToString();
dgvMain.DataSource = bs;
this.Text = text;
bs.ListChanged += new ListChangedEventHandler(bs_ListChanged);
settingsFullPath = settingsDirectory + @"\" + "DataGridView-" + this.Text + ".xml";
dgvMain.ReadOnly = true;
}
So at this point when I run my application I can get a DatagridView populated with the proper DSO child objects AND when a change occurs to a certain object, it reflects properly and updates in the DataGridView. HOWEVER, when I add/remove an object from my list in my Repository, lets say a new DSOTrade, while the window is open, I would expect that change to be reflected in my bindingSource where a new row should be added or a row should disappear, depending on the action taken in the list that is binded to the BindingSource.
This is not happening.
When I take either action, the same number of rows remain.
I took an additional testing step (just added a click even) and so that I could add a breakpoint and compare my bindingSource/Datagridview/ and list from where the objects are coming from. It seems that the Binding list doesn't change it's count to reflect the new/removed item.
Lets say that there were originally 3 rows, and now I added one to my list. I then run my test by setting off that click event, I see that the list (that is sitting in the repository) is properly updated and now has a count of 4, while the BindingSource (and of course the DataGridView) still has a count of 3.
If I were to remove an item (let's say count is 3 again). I run the same test, and the List has a count of 2 and BindingSource still has a count of 3.
One more important thing to note is that my DSOs have a property which state the last update type. When I am about to remove an item from the List the UpdateType for that Property changes to 'DELETE'. This change is actually reflected in my DataGridView, which tells me that the change on the property is still coming through the BidnginSOurce, but the add/removal of the item is not coming through the BindingSource.
Do anyone have any thoughts? Tearing my hair out on this.
Let me know if I need to post any more info.
Thanks.
Edited in Response to Marc Gravell's questions: In my repository I am currently using System.Collections.Generic.List, For each of my lists, they are a List (the parent class for my objects).
I am currently NOT using BindingList in my approach, I am directly assigning My List to a new BindingSource as shown above, however I had also tried BindingList and was getting the same results, after this edit I will make another edit after I retest with BindingList and will post my code.
I am currently handling my ListChanged Event in the following way. I wanted to refresh my DataGridView (dgvMain) upon ListChangedType.ItemAdded or ListChangedType.ItemDeleted (even though refreshing is not helping either, I tested by refreshing in a button click event.), however, the event always seems to fire off as ListChangedType.ItemChanged.
When I add or remove an item NOTHING fires off in the list changed event.
What you see below this event handling code is are the update ticks from the API's server which is currently up and running for the new week.
void bs_ListChanged(object sender, ListChangedEventArgs e)
{
Debug.WriteLine("sender is= " + sender.ToString());
Debug.WriteLine("bs.Datasource= " + bs.DataSource);
Debug.WriteLine("e.ListChangedType = " + e.ListChangedType);
if (e.ListChangedType == ListChangedType.ItemAdded || e.ListChangedType == ListChangedType.ItemDeleted)
{
SystemControlInvoker.InvokeControl(dgvMain, RefreshDGV);
}
}
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List
1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List
1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
sender is= System.Windows.Forms.BindingSource
bs.Datasource= System.Collections.Generic.List
1[Pharaoh_Dashboard.DSOTrade]
e.ListChangedType = ItemChanged
I implement INotifyPropertyChanged in the following way.
public abstract class DSO : IDisposable, INotifyPropertyChanged
{
protected bool _isCreationComplete;
string _dataSourceObjectID;
string _dataSourceID;
protected string _portfolioName;
int _dsoInstance = -1;
protected DSOType _objectType;
protected DSOUpdateType _updateType;
public string DataSourceObjectID
{
get { return _dataSourceObjectID; }
set
{
_dataSourceObjectID = value;
NotifyPropertyChanged("DataSourceObjectID");
}
}
public string DataSourceID
{
get { return _dataSourceID; }
set
{
_dataSourceID = value;
NotifyPropertyChanged("DataSourceID");
}
}
public string PortfolioName
{
get { return _portfolioName; }
set
{
_portfolioName = value;
NotifyPropertyChanged("PortfolioName");
}
}
public DSOType ObjectType
{
get { return _objectType; }
set
{
_objectType = value;
NotifyPropertyChanged("ObjectType");
}
}
public DSOUpdateType UpdateType
{
get { return _updateType; }
set
{
_updateType = value;
NotifyPropertyChanged("UpdateType");
}
}
public bool CreationIsComplete
{
get { return _isCreationComplete; }
set
{
_isCreationComplete = value;
NotifyPropertyChanged("CreationIsComplete");
}
}
public DSO()
{
_isCreationComplete = false;
}
public void SetDSOInstance(int dsoInstance)
{
//do this so it can only be assigned once
if (_dsoInstance == -1)
{
_dsoInstance = dsoInstance;
_dataSourceObjectID = _dataSourceID + "-" + _objectType + "-" + _dsoInstance;
}
}
public void Dispose()
{
//throw new NotImplementedException();
}
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class DSOTrade : DSO
{
int _amount;
string _buySell;
string _instrumentID;
decimal _openRate;
DateTime _openTime;
decimal _commission;
decimal _rolloverInterest;
string _tradeID;
decimal _usedMargin;
decimal _close;
decimal _grossPnL;
decimal _netPnL;
decimal _limit;
decimal _pnl;
decimal _stop;
string _instrument;
bool _isChangeFromInstrumentTick;
TimeSpan _tradeTimeLength;
public string TradeID
{
get { return _tradeID; }
set
{
if (value != _tradeID)
{
_tradeID = value;
NotifyPropertyChanged("TradeID");
}
}
}
public string BuySell
{
get { return _buySell; }
set
{
if (value != _buySell)
{
_buySell = value;
NotifyPropertyChanged("BuySell");
}
}
}
public string InstrumentID
{
get { return _instrumentID; }
set
{
if (value != _instrumentID)
{
_instrumentID = value;
NotifyPropertyChanged("InstrumentID");
}
}
}
public int Amount
{
get { return _amount; }
set
{
if (value != _amount)
{
_amount = value;
NotifyPropertyChanged("Amount");
}
}
}
public decimal OpenRate
{
get { return _openRate; }
set
{
if (value != _openRate)
{
_openRate = value;
NotifyPropertyChanged("OpenRate");
}
}
}
public decimal Commission
{
get { return _commission; }
set
{
if (value != _commission)
{
_commission = value;
NotifyPropertyChanged("Commission");
}
}
}
public decimal RolloverInterest
{
get { return _rolloverInterest; }
set
{
if (value != _rolloverInterest)
{
_rolloverInterest = value;
NotifyPropertyChanged("RolloverInterest");
}
}
}
public decimal UsedMargin
{
get { return _usedMargin; }
set
{
if (value != _usedMargin)
{
_usedMargin = value;
NotifyPropertyChanged("UsedMargin");
}
}
}
public DateTime OpenTime
{
get { return _openTime; }
set
{
if (value != _openTime)
{
_openTime = value;
NotifyPropertyChanged("OpenTime");
}
}
}
//Calculated
public decimal Close
{
get { return _close; }
set
{
if (value != _close)
{
_close = value;
NotifyPropertyChanged("Close");
}
}
}
public decimal PnL
{
get { return _pnl; }
set
{
if (value != _pnl)
{
_pnl = value;
NotifyPropertyChanged("PnL");
}
}
}
public decimal GrossPnL
{
get { return _grossPnL; }
set
{
if (value != _grossPnL)
{
_grossPnL = value;
NotifyPropertyChanged("GrossPnL");
}
}
}
public decimal NetPnL
{
get { return _netPnL; }
set
{
if (value != _netPnL)
{
_netPnL = value;
NotifyPropertyChanged("NetPnL");
}
}
}
public decimal Limit
{
get { return _limit; }
set
{
if (value != _limit)
{
_limit = value;
NotifyPropertyChanged("Limit");
}
}
}
public decimal Stop
{
get { return _stop; }
set
{
if (value != _stop)
{
_stop = value;
NotifyPropertyChanged("Stop");
}
}
}
public string Instrument
{
get { return _instrument; }
set
{
if (value != _instrument)
{
_instrument = value;
NotifyPropertyChanged("Instrument");
}
}
}
public TimeSpan TradeTimeLength
{
get { return _tradeTimeLength; }
set
{
if (value != _tradeTimeLength)
{
_tradeTimeLength = value;
NotifyPropertyChanged("TradeTimeLength");
}
}
}
public bool IsChangeFromInstrumentTick
{
get { return _isChangeFromInstrumentTick; }
set
{
if (value != _isChangeFromInstrumentTick)
{
_isChangeFromInstrumentTick = value;
NotifyPropertyChanged("IsChangeFromInstrumentTick");
}
}
}
public DSOTrade()
{
_objectType = DSOType.Trade;
}
}
OK , finally have found a solution to my problem.
Mahmoud thanks for the suggestion but I ended up having the same issue.
Somewhere in my code I think something (I don't know exactly what) was getting lost when I made the reference from the main list to the binding source. Maybe it was the fact that is was a Generic list that I was using (but even an ObservableCollection was causing the same issue. I got a slightly better understanding from this link where Marc Gravell answers another similar question (see his edit to his answer).
C# Inherited class BindingList<T> doesn't update controls
So I ended up using a ThreadedBindingList as also recommended by Marc in this link.
Just an additional note. Had hit a Cross-Thread Exception at base.OnListChanged(e);
even when using his ThreadedBindingList. This was because the SynchronizationContext was always null when the thread on which the ThreadedBindingList was created was not the UI thread.
See:
Why is SynchronizationContext.Current null?
I got around this by creating a property for SynchronizationContext in the ThreadedBindingList, and assigning it before assigning the ThreadedBindingList to my DataGridView. The version I am now using looks like the following.
public class ThreadedBindingList<T> : BindingList<T>
{
public SynchronizationContext SynchronizationContext
{
get { return _ctx; }
set { _ctx = value; }
}
SynchronizationContext _ctx;
protected override void OnAddingNew(AddingNewEventArgs e)
{
if (_ctx == null)
{
BaseAddingNew(e);
}
else
{
SynchronizationContext.Current.Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (_ctx == null)
{
BaseListChanged(e);
}
else
{
_ctx.Send(delegate { BaseListChanged(e); }, null);
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
}
I now implement ThreadedBindingLists of each Concrete Child Class of 'DSO' in my Repository class.
I assign the DataSource in the following way in my frmDataWindow Form.
//Must set Synchonization Context of the current UI thread otherswise system will throw CrossThread-Exception when tryin gto add/remove a record from the BindingList
switch (_dsoType)
{
case DSOType.Portfolio:
{
ThreadedBindingList<DSOPortfolio> list = _ds.DSORepository.PortfolioBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
break;
}
case DSOType.Instrument:
{
ThreadedBindingList<DSOInstrument> list = _ds.DSORepository.InstrumentBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
break;
}
case DSOType.Trade:
{
ThreadedBindingList<DSOTrade> list = _ds.DSORepository.TradeBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged +=new ListChangedEventHandler(list_ListChanged);
break;
}
case DSOType.ClosedTrade:
{
ThreadedBindingList<DSOClosedTrade> list = _ds.DSORepository.ClosedTradeBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
break;
}
case DSOType.Order:
{
ThreadedBindingList<DSOOrder> list = _ds.DSORepository.OrderBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
break;
}
case DSOType.Position:
{
ThreadedBindingList<DSOPosition> list = _ds.DSORepository.PositionBindingList;
list.SynchronizationContext = SynchronizationContext.Current;
dgvMain.DataSource = list;
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
break;
}
default:
{
break;
}
}