13

Using VS2010 and .NET 4.0 with C# and WinForms:

I always want a Vertical Scrollbar to show for my panel as a disabled scrollbar (when it's not needed, and a enabled one when it can be used.

So it's like a hybrid AutoScroll. I've tried using VScrollBars but I can't figure out where to place them to make this work.

Essentially I've got a user control that acts as a "Document" of controls, its size changes so when using auto-scroll it works perfectly. The scrollbar appears when the usercontrol doesn't fit and the user can move it updown.

It's like a web browser essentially. However, redrawing controls takes a long time (it's forms with many fields and buttons etc within groups in a grid within a panel :P

So anyhow, when autoscroll enables the vertical scrollbar, it takes a while to redraw the window. I'd like to ALWAYS show the vertical scrollbar as indicated above (with the enable/disable functionality).

If anyone has some help, i've read many posts on the subject of autoscroll, but noone has asked what I'm asking and I can't come up with a solution.

user1104203
  • 157
  • 1
  • 1
  • 8
  • 7
    This is ridiculously difficult to do. The code that controls the scrollbars are private methods in ScrollableControl, can't override them. Trying to fake it by docking a VScrollBar in the panel that's hidden when scrolling is needed caused glitches that I couldn't get rid of. I gave up. – Hans Passant Dec 31 '11 at 22:49
  • 1
    Hans is correct. If you want your thing to work robustly, don't trust the answers below. The logic behind the standard `WS_HSCROLL` and `WS_VSCROLL` scroll bars is already hopelessly screwed up in the Win32 API (and this is what's used in `ScrollableControl`). Even if you do try to fix it at that level, it will be full of glitches; Windows insists on taking control over the scroll bars. The answers posted here are fragile at best and will produce unwanted side effects like flicker or layout issues. If you want to do this *properly*, you will have to write a lot of code yourself. – dialer Feb 08 '21 at 01:06

6 Answers6

10

C# Version of competent_Tech's answer

using System.Runtime.InteropServices; 

public class MyUserControl : UserControl
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);

    private enum ScrollBarDirection
    {
        SB_HORZ = 0,
        SB_VERT = 1,
        SB_CTL = 2,
        SB_BOTH = 3
    }

    public MyUserControl()
    {
        InitializeComponent();
        ShowScrollBar(this.Handle, (int) ScrollBarDirection.SB_VERT, true);
    }
}
fiat
  • 15,501
  • 9
  • 81
  • 103
4

You can use the auto-scroll functionality of the panel, you just need to send it a windows message to show the vertical scrollbar:

<DllImport("user32.dll")> _
Public Shared Function ShowScrollBar(ByVal hWnd As System.IntPtr, ByVal wBar As Integer, ByVal bShow As Boolean) As Boolean
End Function

Private Const SB_VERT As Integer = 1


Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ShowScrollBar(Panel1.Handle, SB_VERT, True)
End Sub

The scrollbar will be displayed and appear as though it can be scrolled, but it won't do anything until it is actually ready to scroll. If you disable it, it won't be automatically re-enabled, so this is probably the best approach.

Also, to improve the performance while resizing, you can call SuspendLayout on the panel before updating and ResumeLayout when done.

competent_tech
  • 44,465
  • 11
  • 90
  • 113
  • I tried to do this, but it didn't seem to matter. The suspend/resume layout. Is this method different than simply going .Visible = true on the VerticalScroll object? Because that does not work correctly. The scrollbar is not modified. – user1104203 Jan 01 '12 at 03:48
  • Yes, this method is different than setting `Visible = true` (this is what I tried first, but .Net completely ignores it). – competent_tech Jan 01 '12 at 03:56
3

What worked for me was overriding the CreateParams call and enabling the WS_VSCROLL style.

public class VerticalFlowPanel : FlowLayoutPanel
{
    protected override CreateParams CreateParams
    {
        get
        {
            var cp = base.CreateParams;
            cp.Style |= 0x00200000; // WS_VSCROLL
            return cp;
        }
    }
}

The AutoScroll logic will now adjust the scrolling bounds without ever hiding the scrollbar.

BradJ
  • 31
  • 1
  • Nice and simple workaround, but unfortunately the `AutoScroll` logic only updates the scrolling bounds and doesn't disable the scrollbar when all contents fit inside. Is there a way to implement the enable/disable behavior on top of this? – glopes Aug 30 '16 at 13:08
1

Here is what solved this for me. My case is that I have a panel sandwiched between another three panels with no degree of liberty in any direction. I needed this panel to be so big that the whole structure would go out of my 1920x1080 screen. The solution is actually very simple. For the panel that needs scroll bars set the AutoScroll property to true. Then, add on it another control in the far right far down position (right-bottom position). The control I choose is a label which I made invisible.... And that is all. Now my panel occupies its restricted area, but I can scroll to the size that I needed and use it for the size I need. If you only need horizontal scroll bars add the invisible control outside left, for vertical only far down bottom.

The actual size of the panel is the one you restrict it to when display it, but the virtual size is dictated by the invisible control.

Ajean
  • 5,528
  • 14
  • 46
  • 69
Gogu CelMare
  • 699
  • 6
  • 15
1

This code will draw a disabled vertical scrollbar whenever the built in scrollbar of the Panel is invisible. The codes assumes that

AutoScroll = true;
AutoSize   = false;

The following code is speed-optimized. It does as few as possible in OnPaint().

Derive a class from Panel. Add these member variables:

// NOTE: static variables are not thread safe. 
// But as we have only one GUI thread this does not matter.
static IntPtr mh_ScrollTheme   = IntPtr.Zero;
static int    ms32_ScrollWidth = SystemInformation.VerticalScrollBarWidth;

Win32.RECT mk_ScrollTop;
Win32.RECT mk_ScrollBot;   // coordinates of top scrollbar button
Win32.RECT mk_ScrollShaft; // coordinates of bottom scrollbar button

Then override OnSizeChanged:

protected override void OnSizeChanged(EventArgs e)
{
    base.OnSizeChanged(e);

    Win32.RECT k_ScrollBar = new Win32.RECT(ClientRectangle);
    k_ScrollBar.Left = k_ScrollBar.Right - ms32_ScrollWidth;

    mk_ScrollTop   = new Win32.RECT(k_ScrollBar);
    mk_ScrollBot   = new Win32.RECT(k_ScrollBar);
    mk_ScrollShaft = new Win32.RECT(k_ScrollBar);

    int s32_Upper = k_ScrollBar.Top    + ms32_ScrollWidth;
    int s32_Lower = k_ScrollBar.Bottom - ms32_ScrollWidth;

    mk_ScrollTop  .Bottom = s32_Upper;
    mk_ScrollBot  .Top    = s32_Lower;
    mk_ScrollShaft.Top    = s32_Upper;
    mk_ScrollShaft.Bottom = s32_Lower;
}

And paint the scrollbar when required:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    if (VScroll)
        return; // The 'real' scrollbar is visible
            
    if (mh_ScrollTheme == IntPtr.Zero)
        mh_ScrollTheme = Win32.OpenThemeData(Handle, "SCROLLBAR");

    if (mh_ScrollTheme == IntPtr.Zero)
        return; // The user has disabled themes

    // Draw the disabled vertical scrollbar.
    IntPtr h_DC = e.Graphics.GetHdc();

    // Draw shaft
    const int SBP_UPPERTRACKVERT = 7;
    const int SCRBS_DISABLED     = 4;
    Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_UPPERTRACKVERT, SCRBS_DISABLED, ref mk_ScrollShaft, IntPtr.Zero);

    // Draw top button
    const int SBP_ARROWBTN       = 1;
    const int ABS_UPDISABLED     = 4;
    Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_ARROWBTN, ABS_UPDISABLED,   ref mk_ScrollTop,  IntPtr.Zero);

    // Draw lower button
    const int ABS_DOWNDISABLED   = 8;
    Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_ARROWBTN, ABS_DOWNDISABLED, ref mk_ScrollBot,  IntPtr.Zero);

    e.Graphics.ReleaseHdc(h_DC);
}
Elmue
  • 7,602
  • 3
  • 47
  • 57
0

For some years, the answer of BradJ and fiat worked for me. Now I needed to show the disabled scrollbar look. But I failed to find the correct way… So here is my workaround.

The code bellow just draw the disabled scrollbar at the position of the real scrollbar.

VerticalFlowPanel


THE CODE

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

public class VerticalFlowPanel : FlowLayoutPanel
{
    public VerticalFlowPanel() 
    {
        AutoScroll = true;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        var width = Width;
        var height = Height;

        var vsWidth = SystemInformation.VerticalScrollBarWidth;
        var vsHeight = SystemInformation.VerticalScrollBarArrowHeight;

        var left = width - vsWidth;

        var sbUpper = new Rectangle(left, 0, vsWidth, height / 2);
        var sbLower = new Rectangle(left, sbUpper.Height, vsWidth, height - sbUpper.Height);
        var arUp    = new Rectangle(left, 0, vsWidth, vsHeight);
        var arDown  = new Rectangle(left, height - vsHeight, vsWidth, vsHeight);

        ScrollBarRenderer.DrawUpperVerticalTrack(e.Graphics, sbUpper, ScrollBarState.Disabled);
        ScrollBarRenderer.DrawLowerVerticalTrack(e.Graphics, sbLower, ScrollBarState.Disabled);

        ScrollBarRenderer.DrawArrowButton(e.Graphics, arUp, ScrollBarArrowButtonState.UpDisabled);
        ScrollBarRenderer.DrawArrowButton(e.Graphics, arDown, ScrollBarArrowButtonState.DownDisabled);
    }

    // Necessary to avoid visual artifacts
    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);

        var width = Width;
        var height = Height;

        var vsWidth = SystemInformation.VerticalScrollBarWidth;

        var scrollBounds = new Rectangle(width - vsWidth, 0, vsWidth, height);

        Invalidate(scrollBounds);
    }
}

NOTE

This is not the best solution. But it was easier than migrate my hole solution to WPF…

D.Kastier
  • 2,640
  • 3
  • 25
  • 40