Benutzer mit den meisten Antworten
NullReferenceException bei System.Windows.Forms.DataGridViewCell.CellStateFromColumnRowStates

Frage
-
hallo zusammen,
ich habe eine multithreadanwendung in der unterschiedliche module basierend auf konfigurationen unterschiedliche aufgaben erledigen und zwischenzeitlich immer wieder logeinträge generieren.
diese logeinträge lasse ich im datagridview anzeigen.
damit das dgv nicht überläuft, habe ich eine prüfung eingebaut, dass sobald eine gewisse anzahl an einträgen überschritten wurde, wenige hundert logeinträge gelöscht werden (natürlich nur die alten). es werden pro stunde mehrere tausend logeinträge generiert und am tag werden mehrere male ältere einträge gelöscht.
die anwendung läuft viele tage ohne probleme, bis irgendwann das dgv nicht mehr gezeichnet werden kann (weiße fläche, rotes kreuz) und folgende exception tritt auf:
************** Exception Text **************
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Windows.Forms.DataGridViewCell.CellStateFromColumnRowStates(DataGridViewElementStates rowState)
at System.Windows.Forms.DataGridViewRow.PaintCells(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow, DataGridViewPaintParts paintParts)
at System.Windows.Forms.DataGridViewRow.Paint(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow)
at System.Windows.Forms.DataGridView.PaintRows(Graphics g, Rectangle boundingRect, Rectangle clipRect, Boolean singleHorizontalBorderAdded)
at System.Windows.Forms.DataGridView.PaintGrid(Graphics g, Rectangle gridBounds, Rectangle clipRect, Boolean singleVerticalBorderAdded, Boolean singleHorizontalBorderAdded)
at System.Windows.Forms.DataGridView.OnPaint(PaintEventArgs e)
at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.DataGridView.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)hier ist die funktion:
die einzige stelle, an der eine null-reference-exception auftreten kann, ist beim zugriff auf this.owningColumn.
ich habe keine ahnung, wie es dazu kommen kann, dass der wert null wird.
kann mir jemand helfen den fehler zu beheben?
viele grüße
kraeppy86
internalDataGridViewElementStatesCellStateFromColumnRowStates(DataGridViewElementStatesrowState)
{
Debug.Assert(this.DataGridView != null);
Debug.Assert(this.ColumnIndex >= 0);
DataGridViewElementStatesorFlags = DataGridViewElementStates.ReadOnly | DataGridViewElementStates.Resizable | DataGridViewElementStates.Selected;
DataGridViewElementStatesandFlags = DataGridViewElementStates.Displayed | DataGridViewElementStates.Frozen | DataGridViewElementStates.Visible;
DataGridViewElementStatescellState = (this.owningColumn.State & orFlags);
cellState |= (rowState & orFlags);
cellState |= ((this.owningColumn.State & andFlags) & (rowState & andFlags));
returncellState;
}- Bearbeitet kraeppy86 Mittwoch, 26. Februar 2020 16:03
Antworten
-
Hi,
deine Ausführungen lesen sich erst einmal recht gut.Unklar ist, warum du eine DataTable als DataSource bindest, dann aber über die DataTable iterierst.
Bei deinen Datenmengen würde ich im virtuellen Modus arbeiten. Den Datenpuffer kann man threadsicher gestalten, so dass der Zugriff vom DataGridView und auch von anderen Threads möglich ist. Außerdem ist diese Arbeitsweise sehr schnell.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 2. März 2020 08:28
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Mittwoch, 11. März 2020 10:34
Alle Antworten
-
Hi,
die Schilderung lässt vermuten, dass irgendwo der thread-übergreifende Zugriff nicht sauber in den Eigentümer-Thread des zu verändernden Objektes "umgeleitet" wird.Werden nicht-threadsichere Objekte außerhalb des threads genutzt, in welchem sie instanziiert wurden? Wenn ja, dann wie?
Werden alle Zugriffe auf das DataGridView auch wirklich in den UI-Thread umgeleitet? Das betrifft z.B. auch Zugriffe auf den Datenpuffer, der vom DataGridView als DataSource genutzt wird.
Um den Prozess zu vereinfachen, kann man eine Erweiterungsmethode nutzen, die das "Umleiten" organisiert. Hier mal eine Demo:
using System; using System.ComponentModel; using System.Data; using System.Threading; using System.Windows.Forms; namespace WindowsFormsApp1 { public partial class Form14 : Form { public Form1() { InitializeComponent(); this.Load += new System.EventHandler(this.Form1_Load); } private DataTable dt; private BindingSource bs = new BindingSource(); private DataGridView dgv = new DataGridView() { Dock = DockStyle.Fill }; private BackgroundWorker bg = new BackgroundWorker(); private void Form1_Load(object sender, EventArgs e) { this.Controls.Add(dgv); dt = GetEmptyDataTable(); bs.DataSource = dt; dgv.DataSource = bs; bg.DoWork += Bg_DoWork; bg.RunWorkerAsync(); } private void Bg_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1; i < 101; i++) { var value = DateTime.Now.ToLongTimeString(); dgv.InvokeIfRequired(new Action<Control>((ctl) => { var newRow = bs.AddNew() as DataRowView; newRow["Col1"] = value; })); Thread.Sleep(500); } } private DataTable GetEmptyDataTable() { var dt = new DataTable(); var col = dt.Columns.Add("ID", typeof(int)); col.AutoIncrement = true; col.AutoIncrementSeed = -1; col.AutoIncrementStep = -1; dt.Columns.Add("Col1", typeof(String)); return dt; } } public static class ControlExtensions { public static void InvokeIfRequired(this Control control, Action<Control> action) { if (control.InvokeRequired) control.Invoke(new Action(() => action(control))); else action(control); } } }
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks -
guten morgen :)
vielen dank für die antwort!
also ich habe ein datatable und dieses binde ich als datasource an das dgv.in einem separaten thread lasse ich aus der db die letzten x einträge laden. der nächste schritt ist
dgv.Invoke((Action)delegate(){
// iteriere über das datatable und füge die werte dem datatable hinzu}
beim hinzufügen eines eintrags wird der code ebenfalls vom dgv invoked und eine neue zeile dem angebundenen datatable hinzugefügt.
wenn der benutzer gewisse konfiguration filtern möchte, dann wird die filtermethode wie folgt aufgerufen ("this" ist in diesem fall das usercontrol auf dem das datagridview liegt):
if (this.InvokeRequired)
{
this.BeginInvoke((Action)delegate
{
ApplyFilter(prio);
});
return;
}
ich habe dem CellPaintingEventHandler eine methode zugewiesen, in der ich auf das dgv zurückgreife, um aus einer zelle einen wert auszulesen (basierend darauf ändere ich ggf die hintergrundfarbe). aber da das control im mainthread erstellt und die methode in diesem zugewiesen wurde, ist ja hier kein invoke erforderlich.
also an sich kann ich schon behaupten, dass es keine illegalen threadübergreifenden vorgänge geben kann. wie ich bereits sagte, die anwendung läuft tage ohne probleme und pro tag werden +- 200.000 logeinträge generiert, in der gui lasse ich nicht mehr als 2000 einträge anzeigen und die codezeilen werden oft aufgerufen.
ich werde aber dennoch mal schauen, ob es nicht doch unter welchen umständen auch immer zu einem illegalen aufruf kommen könnte...
was du mir aber vielleicht sagen könntest: was wäre in der filter-methode besser: this.invoke aufrufen, wobei "this" das usercontrol ist auf dem das datagridview liegt oder sollte ich besser dgv.invoke aufrufen?
da bin ich mir noch etwas unsicher, was besser/schöner ist. eigentlich dürfte es doch egal sein, weil sowohl das usercontrol als auch das dgv ja im gleichen threadcontext existieren....!?
viele grüße :)
kraeppy86
-
Hi,
deine Ausführungen lesen sich erst einmal recht gut.Unklar ist, warum du eine DataTable als DataSource bindest, dann aber über die DataTable iterierst.
Bei deinen Datenmengen würde ich im virtuellen Modus arbeiten. Den Datenpuffer kann man threadsicher gestalten, so dass der Zugriff vom DataGridView und auch von anderen Threads möglich ist. Außerdem ist diese Arbeitsweise sehr schnell.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Montag, 2. März 2020 08:28
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Mittwoch, 11. März 2020 10:34