Sunday, April 21, 2013

Unusable UI caused by the Windows Forms Anchor property and window size restrictions

Suppose you make a Windows desktop application using Visual Studio 2012 Express for Desktop and you want to have a resizable window with a textbox that takes up most of the space and a button at the bottom of the window, as shown below:

There are several ways to do this, but if you are not careful then a flaw in Windows Forms will cause some of your users to see an unusable UI where the button is missing, as shown below:

In this post I will document the flaw in Windows Forms and why it happens.

Here are the steps for reproducing the flaw:

  1. In the Control Panel, change the DPI setting to 100%. You can find this under Control Panel > Appearance and Personalization > Display > Change the size of all items, but you can get there faster if you just press the Windows key, type "dpi", and then do the proper keystrokes after that. If you are in Windows 8, you will need to select "Settings" after typing "dpi".
  2. In Visual Studio 2012 Express for Desktop, create a new Windows Forms Application. I used Visual C# but I expect the problem to happen in Visual C++ and Visual Basic too.
  3. Create a Button and position it in the lower-right corner of the form. Set the Anchor property of the button so that it anchors to the bottom and the right. This means that when the Window is resized, the button will move in order to keep two distances constant: the distance between the button's right edge and the window's right edge, and the distance between the button's bottom edge and the window's bottom edge.
  4. Optional: Create a TextBox that takes up the rest of the space on the forum. You will need to set its Multiline property to true, and set its Anchor property so that it anchors on all four sides. It will resize with the window.
  5. Resize the form so that it the form's height is about 100 pixels less than the height of your screen.
  6. Run the project and verify that everything behaves as expected. The entire textbox and button should be visible no matter how you resize the window.
  7. In the Control Panel again, change the DPI setting to 125%. You will have to log off for the change to take affect. You should notice that everything on your desktop appears bigger than it was before.
  8. Run the program that you built earlier. You should probably just find the executable that was built and run that directly instead of starting Visual Studio. The button will be missing.

So what is happening here? There are three features of .NET that are trying to adjust your window:

  1. Anchoring: The anchoring you set for you controls is trying to make certain distances constant. This is probably done in a SizeChanged event handler or something on the form.
  2. Auto-scaling: At startup, when this.ResumeLayout(false); is called in the InitializeComponent method, the Auto-scaling feature detects that the form was designed on a system with a different font size than the system on which the program is now running. This will resize the form and the things inside it.
  3. Window size limiting: Windows Forms does not let you have a window that is taller or wider than the user's screen.

It seems to me like the anchoring and auto-scaling work well together. At startup, the auto-scaling increases your window size to account for the larger font on the user's computer, and expands the window and controls appropriately. However, when the window size limiting procedure runs, it notices that your window is taller than the screen, so it changes the height of the window. Unfortunately, something must be wrong because the anchoring rules do not get applied when the height is decreased. This leaves your button and part of the text box invisible because they are below the bottom edge of the form. This all happens when your application starts up and resizing the window afterward does not help.

This really seems like a bug in the .NET Framework's Windows Forms, and I hope that Microsoft will fix it in the next version. I cannot think of a reason why they would want the anchoring rules to not be applied after the window size limitations are applied.

I tested this at 150% DPI and the problem did not happen.

Summary of the flaw

At startup, if your Windows form gets expanded by auto-scaling to size larger than the user's screen, it will get resized to fit on the screen. Unfortunately, the anchoring rules are not applied after that last resize. If your window's height got decreased, then any elements that were anchored directly to the bottom of the form will have incorrect coordinates. If your window's width got decreased, than any elements anchored directly to the right side of the form will have incorrect coordinates.

Workarounds

Smaller form: The simplest workaround is to make your form smaller. Let's suppose that the smallest monitor size you want to support is 1024x768. If we assume that the largest DPI setting where this problem occurs is 125%, then we can divide those dimensions by 1.25 to get 819x614. Your form needs to be smaller than 819x614 when displayed at normal DPI. Note that this is just the initial, default size of the form; the user can resize it after the application has started if they want to.

Avoid anchoring elements directly to the form: One workaround is to make a Panel and set its Dock property to Fill. Put all your elements inside the panel instead of directly on the form. I tested this and it worked. I think that the docking must be done in a better way than anchoring, so the panel is able to receive some event about its size getting reduced, and correctly apply the anchoring rules to its children.

You can download the Visual Studio projects I used to investigate this here: anchoring.zip. AnchorTest is the one with the bug and AnchorFix is the improved one with a docked panel.

Other flaws related to anchoring

I suspect that there are more problems with anchoring. Here is a list of the ones I have heard of. Are there any more?

  • I have heard that anchoring does not work correctly with Visual Inheritance, which means having a Form or Control that is a subclass of another. See Roland Weigelt's blog post from 2003.

Conclusion

This is a pretty rare problem that is unlikely to affect many users. Most people that have small monitor resolutions should really just use the default DPI setting of 100%. Most people who use a non-standard DPI setting should have enough resolution so that the window size limiting does not happen at startup. If you cannot wait for Microsoft to fix this, there are some workarounds you can use.

22 comments:

  1. Hi, i would like to know if Microsoft fix the anchoring issue. Thanks

    ReplyDelete
  2. Hi David,
    Thanks for this information. I've been plagued by this bug for a couple of days now and its been driving me crazy! Your solution (Panel + dock=fill) worked brilliantly!

    Thanks!!

    Phil

    ReplyDelete
  3. Hi David,
    Excellent article! Well researched.
    Saved a lot of time.

    Thanks a lot,
    Prithwiraj

    ReplyDelete
  4. Hi David,
    My workaround is NOT to set the AnchorStyles to either Bottom or Right in the designer. I manually do it during form loading. Visual Studio is altering the sizes during the save. It has not been fixed in VS 2013 either.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. What I do to go around it is to size the form based on the position and size of key controls when it has to be resized at runtime.

    Here's a code sample for that:

    Sub ResizeForm(FullSize As Boolean)
    Dim BorderWidth As Integer = (Me.Width - Me.ClientSize.Width) / 2
    Dim TitlebarHeight As Integer = Me.Height - Me.ClientSize.Height - 2 * BorderWidth
    If FullSize Then
    'Put Form bottom just below some control.
    Me.Height = somecontrol.Location.Y + somecontrol.Height + 5 + TitlebarHeight + 2 * BorderWidth
    Else
    'Put Form bottom just above some other control.
    Me.Height = someotherctl.Location.Y - 2 + TitlebarHeight + 2 * BorderWidth
    End If
    End Sub

    ReplyDelete
  8. Hi!
    Does someone have updates?
    At the time being with VS 2017 out nothing has changed!!

    ReplyDelete
    Replies
    1. I'm not surprised. I don't think Microsoft or Mono are working on their Windows Forms implementations anymore. You should look into using Qt 5, it's much better and handling window layout and it works cross-platform.

      Delete
    2. Very sad thing. Many are still using them.
      Do you know a workaround even for the toolstrip items images?
      On some forms they scale correctly and in some forms they don't. It's really a pain since the forms have exactly the same properties. Can't figure out.

      Delete
  9. This comment has been removed by the author.

    ReplyDelete
  10. This comment has been removed by a blog administrator.

    ReplyDelete
  11. This comment has been removed by a blog administrator.

    ReplyDelete
  12. This comment has been removed by a blog administrator.

    ReplyDelete
  13. The workaround with a Panel and dock=fill worked perfect for me. I want to add that the problem seems to be occuring under certain circumstances also when display scaling is set to 100%. In my case, the customer from Japan with Japanese language settings had 100% display scaling. The form would have fitted on the screen (700px form height on 768px screen height, enough height left for task bar), but still Windows reduced the form hight, breaking vertical anchoring.

    ReplyDelete
  14. This comment has been removed by a blog administrator.

    ReplyDelete
  15. This comment has been removed by a blog administrator.

    ReplyDelete
  16. This comment has been removed by a blog administrator.

    ReplyDelete
  17. This comment has been removed by a blog administrator.

    ReplyDelete
  18. This comment has been removed by a blog administrator.

    ReplyDelete
  19. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.