I\'m writing an app in .net that uses the autoscroll for a layout panel in a dialog. It seems that whenever I resize the window so that the vertical scrollbars should appear
Despite this being an old question, it is still a problem in .NET 4. Having read as much as I could find on the issue, I have rolled a combination of solutions into a helper class.
First, here is the result that I am shooting for ... I have a panel that contains a variety of controls. The child controls, and their sizes, can change based on user activity. I want the panel to resize horizontally so that there is never a horizontal scroll bar, but if there is not enough room vertically, I want the vertical scroll bar to appear. Further, the vertical scroll bar can not cover any of my child controls when it appears, and I don't want to leave a gap for it when it is not needed.
The two 'bugs' that my helper class attempts to fix are first, never show the horizontal scroll bar, and second, when the vertical scroll bar appears, have the panel's width increase automatically to accommodate it.
My assumptions are that the panel is set to AutoSize and AutoScroll, and the child controls are set to AutoSize as well.
The helper class attaches itself to the panel (by handling the Paint and SizeChanged events) and does two things. First, it disables the horizontal scroll bar. This is not as easy as it sounds, and I found the solution to this problem here Horizontal scroll bar answer by Kbv Subrahmanyam. Second, in response to the Paint and SizeChanged events, as well as a background timer, it checks to see if the Visible property of the vertical scroll bar has changed. If so, the helper class alters the Right Padding property of the panel to add or remove the extra space the scroll bar requires. The use of the various panel events and the timer are required because .NET exposes no events at all for the scroll bar (a big design flaw IMHO).
Once final point is that you can't do anything that changes the size of the panel while handling the SizeChanged event. Bad Stuff(tm) happens if you do. So, if I need to change the padding due to a SizeChanged event, I schedule that change for later.
Anyway, here is the code for the helper class. It assumes you have all the appropriate 'using' statements, including one for System.Threading ...
///
/// This class is intended to beat the AutoSize and AutoScroll features into submission!
///
/// Or, at least getting them to work the way I want them to (which may not be the way
/// others think they should work).
///
/// This class will force a panel that has AutoSize enabled to actually increase its
/// width as appropriate when the AutoScroll Vertical scroll bar becomes visible.
/// I like this better than attempting to 'reserve' space for the Vertical scroll bar,
/// which wastes space when the scroll bar is not needed, and leaves ugly gaps in
/// your user interface.
///
public class AutoScrollFixer
{
///
/// This is the panel we are 'fixing'
///
private Panel _panel;
///
/// This field keeps track of the original value for
/// the right padding property of the panel.
///
private int _originalRightPadding = 0;
///
/// We use this flag to prevent recursion problems.
///
private bool _adjusting = false;
///
/// This flag keeps track of the last known state of the scroll bar.
///
private bool _lastScrollBarVisible = false;
///
/// We use a timer to check the scroll bar state every so often.
/// This is necessary since .NET (in another stunning piece of
/// architecture from Microsoft) provides absolutely no events
/// attached to the scroll bars of a panel.
///
private System.Windows.Forms.Timer _timer = new System.Windows.Forms.Timer();
///
/// Construct an AutoScrollFixer and attach it to the provided panel.
/// Once created, there is no particular reason to keep a reference
/// to the AutoScrollFixer in your code. It will silently do its thing
/// in the background.
///
///
public AutoScrollFixer(Panel panel)
{
_panel = panel;
_originalRightPadding = panel.Padding.Right;
EnableVerticalAutoscroll(_panel);
_lastScrollBarVisible = _panel.VerticalScroll.Visible;
_panel.Paint += (s, a) =>
{
AdjustForVerticalScrollbar();
};
_panel.SizeChanged += (s, a) =>
{
//
// We can't do something that changes the size while handling
// a size change. So, if an adjustment is needed, we will
// schedule it for later.
//
if (_lastScrollBarVisible != _panel.VerticalScroll.Visible)
{
AdjustLater();
}
};
_timer.Tick += (s, a) =>
{
//
// Sadly, the combination of the Paint event and the SizeChanged event
// is NOT enough to guarantee that we will catch a change in the
// scroll bar status. So, as a last ditch effort, we will check
// for a status change every 500 mSecs. Yup, this is a hack!
//
AdjustForVerticalScrollbar();
};
_timer.Interval = 500;
_timer.Start();
}
///
/// Enables AutoScroll, but without the Horizontal Scroll bar.
/// Only the Vertical Scroll bar will become visible when necessary
///
/// This method is based on this StackOverflow answer ...
/// https://stackoverflow.com/a/28583501/2175233
///
///
public static void EnableVerticalAutoscroll( Panel panel )
{
panel.AutoScroll = false;
panel.HorizontalScroll.Enabled = false;
panel.HorizontalScroll.Visible = false;
panel.HorizontalScroll.Maximum = 0;
panel.AutoScroll = true;
}
///
/// Queue AdjustForVerticalScrollbar to run on the GUI thread after the current
/// event has been handled.
///
private void AdjustLater()
{
ThreadPool.QueueUserWorkItem((t) =>
{
Thread.Sleep(200);
_panel.BeginInvoke((Action)(() =>
{
AdjustForVerticalScrollbar();
}));
});
}
///
/// This is where the real work gets done. When this method is called, we will
/// simply set the right side padding on the panel to make room for the
/// scroll bar if it is being displayed, or reset the padding value to
/// its original value if not.
///
private void AdjustForVerticalScrollbar()
{
if (!_adjusting)
{
try
{
_adjusting = true;
if (_lastScrollBarVisible != _panel.VerticalScroll.Visible)
{
_lastScrollBarVisible = _panel.VerticalScroll.Visible;
Padding p = _panel.Padding;
p.Right = _lastScrollBarVisible ? _originalRightPadding + System.Windows.Forms.SystemInformation.VerticalScrollBarWidth + 2 : _originalRightPadding;
_panel.Padding = p;
_panel.PerformLayout();
}
}
finally
{
_adjusting = false;
}
}
}
}