Skip to content

Fix exceptions on datagridview dpi resolution#10

Draft
tobias-richter wants to merge 4 commits into
developfrom
fix/datagridview-dpi-resolution
Draft

Fix exceptions on datagridview dpi resolution#10
tobias-richter wants to merge 4 commits into
developfrom
fix/datagridview-dpi-resolution

Conversation

@tobias-richter
Copy link
Copy Markdown

@tobias-richter tobias-richter commented Apr 17, 2026

Since starting with AEMManager I always faced issues like

image and aem-manager-error-001

The AEMManager then looked like
aem-manager-error-003

WIth support of GitHub CoPilot I am trying to address the issue.

tobias-richter and others added 2 commits April 8, 2026 14:08
- Add WndProc override to detect display setting changes (WM_SETTINGCHANGE)
- Refresh DataGridView and clear selection when resolution changes to reset cached references
- Wrap mouse click handler in try-catch for graceful error handling

Fixes issue where screen resolution changes caused 'object reference not set to an instance' errors in DataGridView cell operations.
…hronization and reset on resolution changes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent exceptions in the WinForms DataGridView when Windows display settings (DPI/resolution) change, primarily by refreshing the grid on a window message and hardening right-click handling.

Changes:

  • Added a WndProc override to refresh dgInstances on WM_SETTINGCHANGE.
  • Wrapped dgInstances_MouseClick right-click handling in try/catch to avoid crashes during/after display changes.
  • Added a new AEMManager.cs helper class intended for DataTable rebinding/synchronization (currently not integrated into the project).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
AEMManager/AemManager.cs Adds WndProc handling and exception-guarded right-click menu logic for dgInstances.
AEMManager.cs Introduces a new DataTable rebinding helper class, but it’s not currently wired into the app/project.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread AEMManager/AemManager.cs
Comment on lines +22 to +24
const int WM_SETTINGCHANGE = 0x001A;

if (m.Msg == WM_SETTINGCHANGE) {
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WM_SETTINGCHANGE is not the Win32 message for display resolution/DPI changes. If the goal is to handle resolution changes use WM_DISPLAYCHANGE (0x007E), and if the goal is DPI changes use WM_DPICHANGED (0x02E0); otherwise this handler may run on unrelated settings changes and miss actual DPI/resolution events.

Suggested change
const int WM_SETTINGCHANGE = 0x001A;
if (m.Msg == WM_SETTINGCHANGE) {
const int WM_DISPLAYCHANGE = 0x007E;
const int WM_DPICHANGED = 0x02E0;
if (m.Msg == WM_DISPLAYCHANGE || m.Msg == WM_DPICHANGED) {

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs Outdated
Comment thread AEMManager/AemManager.cs Outdated
// select instance below mousepointer
DataGridView.HitTestInfo hitTestInfo = this.dgInstances.HitTest(e.X, e.Y);
if (hitTestInfo.RowIndex >= 0) {
dgInstances.CurrentCell.Selected = false;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dgInstances.CurrentCell can be null (e.g., grid has no current cell after a DPI/layout change). Calling dgInstances.CurrentCell.Selected will then throw; wrapping the whole handler in a broad try/catch hides this. Add a null-check (and only clear selection when a current cell exists) so right-click selection is deterministic without relying on exception handling.

Suggested change
dgInstances.CurrentCell.Selected = false;
DataGridViewCell currentCell = dgInstances.CurrentCell;
if (currentCell != null) {
currentCell.Selected = false;
}

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs
Comment on lines +271 to +272
if (e.Button == MouseButtons.Right)
{
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brace/formatting style in this block (e.g., if (e.Button == MouseButtons.Right) with the opening brace on the next line) is inconsistent with the surrounding file’s predominant if (...) { style. Consider aligning this with the existing formatting to keep diffs smaller and readability consistent.

Suggested change
if (e.Button == MouseButtons.Right)
{
if (e.Button == MouseButtons.Right) {

Copilot uses AI. Check for mistakes.
Comment thread AEMManager.cs
Comment on lines +1 to +6
// Add proper synchronization and data binding reset for DataTable to prevent index corruption on resolution changes.

public class AEMManager
{
private readonly object _lock = new object();

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new file appears to be unused: it is not included in AEMManager/AEMManager.csproj (which lists sources explicitly), so it will not be compiled or address the reported DataGridView issue. Either remove it, or add it to the project and wire it into the form code where the DataTable/DataGridView binding is actually managed.

Copilot uses AI. Check for mistakes.
Comment thread AEMManager.cs
Comment on lines +2 to +21

public class AEMManager
{
private readonly object _lock = new object();

public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
// Synchronize access to the DataTable
lock (_lock)
{
// Reset data binding
dataTable.Clear();
// Rebind data after changing resolution
BindDataTable(dataTable);
}
}

private void BindDataTable(DataTable dataTable)
{
// Your logic to bind the DataTable
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AEMManager.cs defines AEMManager in the global namespace and references DataTable without a using System.Data; (and BindDataTable is an empty stub). If this class is intended to be compiled/used, put it under the existing AEMManager namespace, add required usings, and replace the stub with real binding logic; otherwise it’s dead code that may confuse future maintainers.

Suggested change
public class AEMManager
{
private readonly object _lock = new object();
public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
// Synchronize access to the DataTable
lock (_lock)
{
// Reset data binding
dataTable.Clear();
// Rebind data after changing resolution
BindDataTable(dataTable);
}
}
private void BindDataTable(DataTable dataTable)
{
// Your logic to bind the DataTable
using System;
using System.Data;
namespace AEMManager
{
public class AEMManager
{
private readonly object _lock = new object();
public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
if (dataTable == null)
{
throw new ArgumentNullException(nameof(dataTable));
}
// Synchronize access to the DataTable
lock (_lock)
{
// Reset data binding
dataTable.Clear();
// Rebind data after changing resolution
BindDataTable(dataTable);
}
}
private void BindDataTable(DataTable dataTable)
{
if (dataTable == null)
{
throw new ArgumentNullException(nameof(dataTable));
}
// Finalize the reset so the table is left in a consistent bound state.
dataTable.AcceptChanges();
}

Copilot uses AI. Check for mistakes.
Enhance stability by adding null checks and try-catch blocks around DataGridView operations. Prevent UI flicker during display changes with SuspendLayout/ResumeLayout. Ensure safe selection and refresh logic, reducing unhandled exceptions from data binding issues.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread AEMManager.cs
Comment on lines +3 to +22
public class AEMManager
{
private readonly object _lock = new object();

public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
// Synchronize access to the DataTable
lock (_lock)
{
// Reset data binding
dataTable.Clear();
// Rebind data after changing resolution
BindDataTable(dataTable);
}
}

private void BindDataTable(DataTable dataTable)
{
// Your logic to bind the DataTable
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new top-level AEMManager.cs file looks like a placeholder/prototype: it has no namespace, references DataTable without a using/import, BindDataTable is empty, and it’s not included in the WinForms project’s .csproj (which uses explicit entries). Keeping it in the repo is confusing and suggests functionality that isn’t actually wired in—either remove it or move it into the AEMManager project with the correct namespace/usings and real integration.

Suggested change
public class AEMManager
{
private readonly object _lock = new object();
public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
// Synchronize access to the DataTable
lock (_lock)
{
// Reset data binding
dataTable.Clear();
// Rebind data after changing resolution
BindDataTable(dataTable);
}
}
private void BindDataTable(DataTable dataTable)
{
// Your logic to bind the DataTable
}
using System;
using System.Data;
namespace AEMManager
{
public class AEMManager
{
private readonly object _lock = new object();
public void UpdateDataTableOnResolutionChange(DataTable dataTable)
{
if (dataTable == null)
{
throw new ArgumentNullException(nameof(dataTable));
}
// Synchronize access to the DataTable.
lock (_lock)
{
// Reset the current table contents. Rebinding, if required by the UI,
// must be performed by the caller or by the layer that owns the binding.
dataTable.Clear();
}
}
}

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs
Comment on lines +24 to +38
if (m.Msg == WM_SETTINGCHANGE) {
// Refresh the DataGridView when display settings change
try {
if (dgInstances.DataSource != null) {
dgInstances.SuspendLayout();
dgInstances.ClearSelection();
dgInstances.Refresh();
dgInstances.ResumeLayout();
}
} catch {
// Silently handle any refresh errors
}
}

base.WndProc(ref m);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In WndProc, SuspendLayout()/ResumeLayout() aren’t protected by a finally. If ClearSelection() or Refresh() throws, ResumeLayout() is skipped and the grid can remain permanently suspended. Wrap the suspend/refresh sequence in try/finally, and consider calling base.WndProc(ref m) before doing the refresh so the control has already processed the setting change. Also avoid an empty catch here—log the exception (or at least the message) so real issues aren’t silently hidden.

Suggested change
if (m.Msg == WM_SETTINGCHANGE) {
// Refresh the DataGridView when display settings change
try {
if (dgInstances.DataSource != null) {
dgInstances.SuspendLayout();
dgInstances.ClearSelection();
dgInstances.Refresh();
dgInstances.ResumeLayout();
}
} catch {
// Silently handle any refresh errors
}
}
base.WndProc(ref m);
base.WndProc(ref m);
if (m.Msg == WM_SETTINGCHANGE) {
// Refresh the DataGridView when display settings change
try {
if (dgInstances.DataSource != null) {
dgInstances.SuspendLayout();
try {
dgInstances.ClearSelection();
dgInstances.Refresh();
} finally {
dgInstances.ResumeLayout();
}
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine("Error refreshing dgInstances after WM_SETTINGCHANGE: " + ex.Message);
}
}

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs
Comment on lines 223 to 234
public AemInstance SelectedInstanceInListview {
get {
DataGridViewRow gridRow = this.dgInstances.CurrentRow;
if (gridRow == null) {
try {
if (dgInstances.CurrentRow == null || dgInstances.CurrentRow.DataBoundItem == null) {
return null;
}
DataRow row = (DataRow)((DataRowView)dgInstances.CurrentRow.DataBoundItem).Row;
return (AemInstance)row["Instance"];
}
catch {
return null;
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SelectedInstanceInListview currently relies on a broad try/catch for normal control flow. It would be more robust to use safe casts/pattern matching (e.g., DataBoundItem as DataRowView, row["Instance"] as AemInstance) and return null when the expected types aren’t present, while logging unexpected exceptions. Swallowing all exceptions here can mask real binding issues and make future debugging harder.

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs
Comment on lines 285 to 357
private void dgInstances_MouseClick(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Right) {

// select instance below mousepointer
DataGridView.HitTestInfo hitTestInfo = this.dgInstances.HitTest(e.X, e.Y);
if (hitTestInfo.RowIndex >= 0) {
dgInstances.CurrentCell.Selected = false;
dgInstances.CurrentCell = dgInstances[0, hitTestInfo.RowIndex];
}
try {
if (e.Button == MouseButtons.Right)
{

// select instance below mousepointer
DataGridView.HitTestInfo hitTestInfo = this.dgInstances.HitTest(e.X, e.Y);
if (hitTestInfo.RowIndex >= 0 && hitTestInfo.RowIndex < dgInstances.Rows.Count) {
if (dgInstances.CurrentCell != null) {
dgInstances.CurrentCell.Selected = false;
}
dgInstances.CurrentCell = dgInstances[0, hitTestInfo.RowIndex];
}

AemInstance instance = this.SelectedInstanceInListview;
if (instance == null) {
return;
}
AemInstance instance = this.SelectedInstanceInListview;
if (instance == null) {
return;
}

// Context-Men� initialisieren
List<MenuItem> menuItems = new List<MenuItem>();
MenuItem item;
// Context-Men� initialisieren
List<MenuItem> menuItems = new List<MenuItem>();
MenuItem item;

item = new MenuItem();
item.Text = "&Add...";
item.Click += new EventHandler(addToolStripMenuItem_Click);
menuItems.Add(item);
item = new MenuItem();
item.Text = "&Add...";
item.Click += new EventHandler(addToolStripMenuItem_Click);
menuItems.Add(item);

item = new MenuItem();
item.Text = "&Edit...";
item.DefaultItem = true;
item.Click += new EventHandler(editToolStripMenuItem_Click);
menuItems.Add(item);
item = new MenuItem();
item.Text = "&Edit...";
item.DefaultItem = true;
item.Click += new EventHandler(editToolStripMenuItem_Click);
menuItems.Add(item);

item = new MenuItem();
item.Text = "&Duplicate...";
item.Click += new EventHandler(copyToolStripMenuItem_Click);
menuItems.Add(item);
item = new MenuItem();
item.Text = "&Duplicate...";
item.Click += new EventHandler(copyToolStripMenuItem_Click);
menuItems.Add(item);

item = new MenuItem();
item.Text = "&Remove";
item.Click += new EventHandler(removeToolStripMenuItem_Click);
menuItems.Add(item);
item = new MenuItem();
item.Text = "&Remove";
item.Click += new EventHandler(removeToolStripMenuItem_Click);
menuItems.Add(item);

item = new MenuItem();
item.Text = "-";
menuItems.Add(item);
item = new MenuItem();
item.Text = "-";
menuItems.Add(item);

item = new MenuItem();
item.Text = "&Show in Taskbar";
item.Click += new EventHandler(setShowInTaskbarToolStripMenuItem_Click);
item.Checked = instance.ShowInTaskbar;
menuItems.Add(item);
item = new MenuItem();
item.Text = "&Show in Taskbar";
item.Click += new EventHandler(setShowInTaskbarToolStripMenuItem_Click);
item.Checked = instance.ShowInTaskbar;
menuItems.Add(item);

dgInstances.ContextMenu = new ContextMenu(menuItems.ToArray());
dgInstances.ContextMenu = new ContextMenu(menuItems.ToArray());

dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddControlMenuItems(dgInstances.ContextMenu.MenuItems, instance);
dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddControlMenuItems(dgInstances.ContextMenu.MenuItems, instance);

dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddOpenMenuItems(dgInstances.ContextMenu.MenuItems, instance, false);
dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddOpenMenuItems(dgInstances.ContextMenu.MenuItems, instance, false);

dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddLogMenuItems(dgInstances.ContextMenu.MenuItems, instance);
dgInstances.ContextMenu.MenuItems.Add("-");
AemActions.AddLogMenuItems(dgInstances.ContextMenu.MenuItems, instance);

dgInstances.ContextMenu.Show(dgInstances, e.Location);
dgInstances.ContextMenu.Show(dgInstances, e.Location);
}
}
catch (Exception ex)
{
// Handle resolution change errors gracefully
System.Diagnostics.Debug.WriteLine("Mouse event error: " + ex.Message);
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dgInstances_MouseClick handler wraps the entire method in a broad try/catch and only writes ex.Message via Debug.WriteLine. This can hide real failures (and loses stack traces). Prefer catching only the specific operations that can fail during DPI/rebinding, and log via the project’s log4net logger (including the exception) so issues can be diagnosed in production.

Copilot uses AI. Check for mistakes.
Comment thread AEMManager/AemManager.cs
Comment on lines +364 to 375
try {
foreach (DataGridViewRow gridRow in dgInstances.Rows) {
if (gridRow.DataBoundItem != null) {
DataRow row = (DataRow)((DataRowView)gridRow.DataBoundItem).Row;
AemInstance instance = (AemInstance)row["Instance"];
row["Status"] = instance.GetStatusText();
}
}
}
catch {
// Handle any data binding errors during refresh
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timerStatusRefresh_Tick wraps the entire row iteration in a single try/catch, so one bad row/binding state will stop status updates for all rows until the next tick, and the exception is silently swallowed. Consider handling exceptions per-row (or skipping only invalid DataBoundItem rows) and logging unexpected failures; also guard against the grid being disposed/handle not created during resolution changes.

Suggested change
try {
foreach (DataGridViewRow gridRow in dgInstances.Rows) {
if (gridRow.DataBoundItem != null) {
DataRow row = (DataRow)((DataRowView)gridRow.DataBoundItem).Row;
AemInstance instance = (AemInstance)row["Instance"];
row["Status"] = instance.GetStatusText();
}
}
}
catch {
// Handle any data binding errors during refresh
}
if (this.dgInstances == null || this.dgInstances.IsDisposed || this.dgInstances.Disposing || !this.dgInstances.IsHandleCreated) {
return;
}
foreach (DataGridViewRow gridRow in dgInstances.Rows) {
if (gridRow == null) {
continue;
}
try {
DataRowView rowView = gridRow.DataBoundItem as DataRowView;
if (rowView == null) {
continue;
}
DataRow row = rowView.Row;
if (row == null || !row.Table.Columns.Contains("Instance")) {
continue;
}
AemInstance instance = row["Instance"] as AemInstance;
if (instance == null) {
continue;
}
row["Status"] = instance.GetStatusText();
}
catch (Exception ex) {
System.Diagnostics.Debug.WriteLine("Status refresh error for row " + gridRow.Index + ": " + ex.Message);
}
}

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@stefanseifert
Copy link
Copy Markdown
Member

that's strange - i've never seen this type of error message.
i'm wondering what special setup on your machine leads to this problem.

@tobias-richter
Copy link
Copy Markdown
Author

@stefanseifert I am fighting with this now for years and had this issue on several computers. I suspect another program influencing this behavior. Now with AI I thought I can tackle it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants