1

I have a Windows Forms application (.NET Framework 4.8) which has a TabControl tabControl as the main object. I use it to switch to different screens, and I have a next and previous button which select the next or previous tab with this code:

private void ButtonNextTab(object sender, EventArgs e)
{
    int index = tabControl.SelectedIndex;
    tabControl.SelectTab(index + 1); // index - 1 for ButtonPreviousTab
}

I set my application as DPI aware by including the following in app.config:

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>

On the last tab, I have a DataGridView. If I go to this last tab exactly twice using the buttons, no matter how fast I click the buttons, I get the following exception: System.ComponentModel.Win32Exception: 'Error creating window handle.', and it appears to be thrown in the SelectTab method. It also happens if I replace SelectTab with tabControl.SelectedIndex = index + 1.

However, it does not happen if I remove the tag in app.config or navigate to the tab any number of times using the TabControl's tabs. I create and run the application on a second monitor with 100% scaling, but if I run it on my main laptop screen with 125% scaling the problem does not occur (although interestingly the application always starts at 100% until a DPI change event seems to be triggered by moving the application from one screen to the other). I have only been able to recreate this problem on my 100% screen and never on my 125% screen. If I set scaling on my primary screen to 100% the problem vanishes, and if both screens are set to 125% I can't reproduce it either.

I could not find any information about this problem. Is there a way to prevent it?

Image of the exception information, barely contains any useful information


Update: I tried setting DataGridView.DisableHighDpiImprovements to "false", that didn't fix it


Update 2: the complete stack trace (the image only shows the first bit):

at System.Windows.Forms.NativeWindow.CreateHandle(CreateParams cp)
at System.Windows.Forms.Control.CreateHandle()
at System.Windows.Forms.ComboBox.CreateHandle()
at System.Windows.Forms.Control.RecreateHandleCore()
at System.Windows.Forms.ComboBox.RecreateHandleCore()
at System.Windows.Forms.ComboBox.OnFontChanged(EventArgs e)
at System.Windows.Forms.Control.set_Font(Font value)
at System.Windows.Forms.Control.WmDpiChangedBeforeParent(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ComboBox.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.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

For reference, this is what the datagridview looks like (with text in the header removed), the buttons on the right aren't part of the datagridview but are used to control the currently selected row.

enter image description here


Update 3: I can't reproduce it myself when creating a new project. Restarting at different DPI values (100% and 125%) didn't help. I found that the dropdown background is black the first time it is clicked. If an option is moused over it appears as a normal highlighted option (blue background and white text) until I leave it again. This happens on both screens.

black dropdown menu

I also found that if I place my cursor in both of the text fields in the placeholder DataDridViewRow, no exception occurs. After placing the cursor in one of those text fields the text field itself appears black like the dropdown does, but only once (unlike the dropdown) and afterwards the dropdown and other textbox are no longer black. This would suggest to me that there's something wrong with the placeholder row, but I can't find out what and I don't know how it relates to different DPI issues. One of the columns also had a really long tooltip I thought might cause issues, but removing it did nothing.


Update 4: If the DataGridView's Visible property is set to false in the form designer, no error occurs. That seems to restrict the problem to something related to both the DataGridView and the different scaling on multiple screens. I also tried programmatically setting text in the two text fields of the placeholder row, which didn't work.

asdf101
  • 569
  • 5
  • 18
  • 1
    If you add this in OnLoad: `foreach (var ctl in tabControl.TabPages.OfType()) { if (!ctl.IsHandleCreated) ctl.Visible = true; }`, do you have an exception right here, after, or no exceptions at all? – Jimi Apr 20 '21 at 13:02
  • @Jimi that doesn't change anything, I still get the Win32Exception in the ButtonNextTab method – asdf101 Apr 20 '21 at 13:40
  • That loop set the Visible property of each TabPage to `true`. This causes the creation of the handle of all child Controls in each TabPage that are set to `Visible = true` in the Designer. If some Control are set to `Visible = false`, the handle is not created. -- Are you trying to access, when the TabPage changes, Controls that are not visible? Do you change the visible Property at some point? Do you have Custom Controls / third-party Controls there? Do you create and add child Controls at run-time? Some other relevant operation when the Tab changes? -- What .Net version is your app built on? – Jimi Apr 20 '21 at 13:56
  • @Jimi no, all controls are visible at all times, and I only use default windows forms elements. I suppose the datagridview creates datagridviewrows with their respective controls, but nothing else. I use .NET Framework 4.8. – asdf101 Apr 20 '21 at 13:58
  • + If he problem is related to the creation of the handle of a child Control of a TabPage, you should get the exception when you create the handle directly, with that code, too. There's a NativeWindow that causes the exception while calling CreateParams. See what that is (it could be a component). -- You should also show when and where the DataSource of your DGV is set. If you have Columns created at Design-Time, all their handles are created when the parent Control creates its own handle. – Jimi Apr 20 '21 at 13:59
  • The datagridview has no source and columns can't be created, only rows – asdf101 Apr 20 '21 at 14:12
  • That's really not enough information on what happens when you switch TabPage. I cannot replicate the problem. – Jimi Apr 20 '21 at 14:38
  • Try posting the entire stack trace. Why doesn't your navigation code check if you are on the last tab? Or the first one? – LarsTech Apr 20 '21 at 14:43
  • @LarsTech added the stack trace as en edit. I don't have a previous button on the first tab or a next button on the last one, so I didn't bother checking for those situations. – asdf101 Apr 20 '21 at 14:48
  • @Jimi do you have two monitors at different scaling? Because I suspect that's were the problem comes from somehow – asdf101 Apr 20 '21 at 14:49
  • ah. We can't see what you claim in your question. The dialog thing to check the current tab page index? Nothing here related to DPI thing. You are seeing the exception of the first called method. Anyways, try to elaborate so someone else provide you with the correct answer. – dr.null Apr 20 '21 at 20:15
  • @dr.null I only added those just now to check if you were right about the wrong indices, printing is too much effort in a form application – asdf101 Apr 20 '21 at 20:20
  • No problem. Forget the DPI thing for now and focus on the first comment here. If you have code that needs a handle to be created, then don't execute it if the `IsHandleCreated` property of the target control returns `false`. – dr.null Apr 20 '21 at 20:32
  • @dr.null I don't see how ignoring dpi would help me given that it seems to be strongly tied to whether or not I get the Win32Exception, and nothing in the code I'm executing here relies on handles. I tried putting `dataGridView.IsHandleCreated` in the ButtonNextTab method, which always returned true after the first time I visited the tab with the DGV. Forcing the handle to be created with `_ = dataGridView.Handle` didn't work either. – asdf101 Apr 21 '21 at 09:37
  • Maybe but nothing here helps to tell. – dr.null Apr 21 '21 at 12:53

1 Answers1

0

I found a workaround, by manually selecting the first text field in the datagridview if no cell is selected the problem no longer occurs:

private void ButtonNextTab(object sender, EventArgs e)
{
    int index = tabControl.SelectedIndex;
    if (index == 1 && dataGridView.CurrentCell is null)
    {
        dataGridView.Rows[0].Cells[1].Selected = true;
    }
    tabControl.SelectTab(index + 1);
}

I still have no idea what the actual problem is or how this workaround prevents that from occurring, but at least it works. I will probably switch to WPF if I ever find the time to rewrite everything. If someone comes across this and figures out what caused the problem, please add it as answer.

asdf101
  • 569
  • 5
  • 18