Источник привязки НЕ отражает новую / удаленную строку, когда источник данных List добавляет / удаляет элемент
Поэтому я попытался использовать BindingList и BindingSource, но проблема одинакова для обоих.
Просто для справки: у меня есть приложение, где я получаю обновления торговых объектов от API. Я получаю обновления в реальном времени от API (может быть добавить / обновить / удалить updateType) и обработать их в классе репозитория, который имеет списки каждого соответствующего типа объекта, которые все наследуются от вызова родительского класса DSO(для DataSourceObject).
Этот репозиторий имеет экземпляр в другом классе с именем DataSource (я упомяну это позже).
Таким образом, у меня есть несколько списков в моем репозитории, который находится в DataSource, и все операции (добавление / удаление / обновление) прекрасно работают с этими списками до этого момента.
Теперь в моем пользовательском интерфейсе есть форма frmDashboard, которая вызывает форму frmDataWindow.
Это окно frmDataWindow имеет DataGridView, где я хочу показать мои различные объекты дочернего класса DSO(3 примера: DSOPorfolio, DSOInstrument, DSOTrade).
Вот где у меня проблемы, я пробовал разные методы, но в настоящее время я использую следующий подход:
Я объявляю новый экземпляр frmDataWindow, в отдельном методе я передаю ссылку на DataSource (или, как я считаю, ссылку, потому что я понимаю, что C# передает все по ссылке как значение по умолчанию) экземпляру frmDataWindow. Этот источник данных уже имеет репозиторий с загруженными списками моих BusinessObjects.
Затем экземпляру frmDataWindow я передаю тип объекта через enum (назовем его DSOType), который я хочу связать с DataGridView.
Затем я запускаю оператор switch, который назначает список объектов DSO источнику привязки, одновременно преобразуя его в соответствующий тип дочернего класса DSO(поэтому все свойства отображаются в DataGridView).
Просто обратите внимание, что я уже реализовал INotifyPropertyChanged во всех моих объектах DSO.
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;
}
Таким образом, в этот момент, когда я запускаю свое приложение, я могу получить DatagridView, заполненный соответствующими дочерними объектами DSO, И когда изменение происходит с определенным объектом, оно корректно отражается и обновляется в DataGridView. ОДНАКО, когда я добавляю / удаляю объект из моего списка в моем репозитории, скажем, новый DSOTrade, пока окно открыто, я ожидал бы, что это изменение будет отражено в моем bindingSource, где должна быть добавлена новая строка или строка должна исчезают, в зависимости от действия, выполненного в списке, который связан с BindingSource.
Этого не происходит.
Когда я выполняю какое-либо действие, остается одинаковое количество строк.
Я предпринял дополнительный шаг тестирования (просто добавил четность клика), чтобы добавить точку останова и сравнить свой bindingSource / Datagridview / и список, откуда поступают объекты. Кажется, что список привязок не меняет своего счетчика, чтобы отразить новый / удаленный элемент.
Допустим, изначально было 3 строки, а теперь я добавил одну в свой список. Затем я запускаю свой тест, отключив событие click, и вижу, что список (который находится в хранилище) правильно обновлен и теперь имеет счетчик 4, в то время как BindingSource (и, конечно, DataGridView) по-прежнему имеет счетчик. из 3.
Если бы я должен был удалить элемент (скажем, количество снова 3). Я запускаю тот же тест, и список имеет счетчик 2, а BindingSource - счетчик 3.
Еще одна важная вещь, на которую следует обратить внимание, - это то, что мои DSO имеют свойство, которое указывает тип последнего обновления. Когда я собираюсь удалить элемент из списка, UpdateType для этого свойства изменится на "DELETE". Это изменение фактически отражено в моем DataGridView, который говорит мне, что изменение свойства все еще происходит через BidnginSOurce, но добавление / удаление элемента не происходит через BindingSource.
У кого-нибудь есть мысли? Вырвать мои волосы на этом.
Дайте мне знать, если мне нужно больше информации.
Благодарю.
Отредактировано в ответ на вопросы Марка Гравелла: В моем репозитории я в настоящее время использую System.Collections.Generic.List. Для каждого из моих списков они представляют собой List (родительский класс для моих объектов).
В настоящее время я НЕ использую BindingList в своем подходе, я непосредственно назначаю Мой список новому BindingSource, как показано выше, однако я также пробовал BindingList и получал те же результаты, после этого редактирования я выполню еще одно редактирование после повторного тестирования с BindingList и опубликую мой код.
В настоящее время я обрабатываю событие ListChanged следующим образом. Я хотел обновить свой DataGridView (dgvMain) для ListChangedType.ItemAdded или ListChangedType.ItemDeleted (хотя обновление тоже не помогает, я проверял, обновляя в событии нажатия кнопки.), Однако это событие всегда срабатывает как ListChangedType.ItemChanged.
Когда я добавляю или удаляю элемент, в списке измененных событий НИЧТО не срабатывает.
Ниже вы видите этот код обработки событий - это тики обновления с сервера API, который в настоящее время работает и работает в течение новой недели.
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);
}
}
отправитель = 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
Я реализую INotifyPropertyChanged следующим образом.
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;
}
}
2 ответа
ОК, наконец-то нашли решение моей проблемы.
Махмуд, спасибо за предложение, но у меня возникла такая же проблема.
Где-то в моем коде я думаю, что что-то (я не знаю точно, что) терялось, когда я делал ссылку из основного списка на источник привязки. Возможно, это был тот факт, что это был общий список, который я использовал (но даже ObservableCollection вызывал ту же проблему. Я получил немного лучшее понимание по этой ссылке, где Марк Гравелл отвечает на другой похожий вопрос (см. Его правку к своему ответу),
C# Унаследованный класс BindingList
В итоге я использовал ThreadedBindingList, как это было рекомендовано Марком в этой ссылке.
Просто дополнительная заметка. Попал в исключение Cross-Thread в base.OnListChanged(e);
даже при использовании его ThreadedBindingList. Это произошло потому, что SynchronizationContext всегда был нулевым, когда поток, в котором был создан ThreadedBindingList, не был потоком пользовательского интерфейса. См.: Почему SynchronizationContext.Current является нулевым?
Я справился с этим, создав свойство для SynchronizationContext в ThreadedBindingList и назначив его перед назначением ThreadedBindingList для моего DataGridView. Версия, которую я сейчас использую, выглядит следующим образом.
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);
}
}
Теперь я реализую ThreadedBindingLists каждого конкретного дочернего класса 'DSO' в моем классе репозитория.
Я назначаю DataSource следующим образом в моей форме frmDataWindow.
//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;
}
}
Не уверен, какой.net Framework вы используете. Но если вы сможете использовать ObservableCollection, это облегчит вашу жизнь, потому что этот тип уже реализует INotifyPropetyChanged.
Посмотрите на https://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
Список не реализует INotifyPropertyChanged и не имеет внутреннего механизма для распространения информации об изменении его внутреннего списка. Вы можете проверить ссылку ниже, чтобы проверить. https://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.110).aspx