Windows Develop Bookmark and Share   
 index > Windows Forms General > Scrollbar issue (.NET 2.0, C#)
 

Scrollbar issue (.NET 2.0, C#)

I know that user can never reach scrollbar's Maximum value - MSDN states that clearly. Only Maximum minus LargeChange can be reached. But - when scrolling by thumb (not by arrows or keyboard) - Maximum minus LargeChange is reached only when user releases the thumb! And the same for Minimum. After depressing mouse button, the scrollable contents "jumps" and the corresponding edge is reached.
As a result, user will never see edges of the scrollable area until he/she depresses mouse button - and that is not good.
To reproduce this problem, add a VScrollBar to a Windows Form, and add the following code to its Scroll event handler:
    Console.WriteLine(this.vScrollBar1.Value);
Start the project, move the thumb to the middle, then move it to the very top (i.e. reach the Minimum) and look at the "Output" window: you'll see there "1", or maybe "2", or maybe "3"... then release the thumb (depress mouse button) and look at the "Output" window again: you'll see there "0" (=Minimum). Why Minimum is reached only when thumb is released?! If I use scrollbar to create a scrollable control - then I get ugly behavior, because in the "scroll-handling portion" of my OnPaint code I'm looking (of course!) at the Value property! and - as shown above - this property reports edges only after thumb is released.

Any ideas?
Comanche  Friday, October 02, 2009 11:26 AM
Okay, I figured out what is going on.  The problem is using the scrollbar's Value property in the Scroll event.  It hasn't been updated yet, you must use e.NewValue instead.

Hans Passant.
nobugz  Sunday, October 04, 2009 5:08 AM
Don't call CreateWindowEx yourself, override CreateParams to set the arguments that Control will use to call CreateWindowEx.  Use Reflector to see how HScrollBar does it.

Hans Passant.
nobugz  Sunday, October 04, 2009 5:36 PM
Sigh.  Use SetStyle to turn off UserPaint.

Hans Passant.
nobugz  Sunday, October 04, 2009 7:09 PM
Yeah, Windows scrollbars are a bit screwy.  The maximum value is actually Maximum - LargeChange + 1.  And the thumb track can be off by more than 1, it depends how fast you move the thumb.  I'm not aware of any workaround for this.  Try to accommodate this by adding some padding to either end of the displayed info.  If the jump is too noticeable then the painting probably takes too long.  Fight that by redrawing only when you get ScrollEventArgs.Type == EndScroll.  Not perfect, to be sure.
Hans Passant.
nobugz  Friday, October 02, 2009 12:57 PM
>> If the jump is too noticeable then the painting probably takes too long.

  I do all painting via GDI32, on a virtual DC, which is finally copied to UserControl DC with one BitBlt call. What can be faster? And this painting is very simple, by the way. No complicated functions - just DrawEdge, LineTo, DrawText and FillRect. OnPaint does not contain creation of DC/bitmap, and all GDI objects (brushes, pens, fonts) are cached and so created only once.

>> Fight that by redrawing only when you get ScrollEventArgs.Type == EndScroll.

  Alas, I can't do that. I need real-time redraw during user scrolling. BTW, there's no any flickering while moving the thumb, I would even say that scrolling looks perfectly except... the issue described above. So I don't think it's related to performance.

PS: I tested VB6 scrollbar (from the built-in set of controls) - there is no such problem there. AFAIK, this control is just a COM-wrapper over standard Win32 "ScrollBar" window. So I think it's worth trying to create a .NET-wrapper over the same window. I also tried to use my GDI32-painting together with WinForms "AutoScroll" feature: problem is also absent but some flickering is present - and this is absolutely not acceptable in my case.
Comanche  Friday, October 02, 2009 3:01 PM
>> And the thumb track can be off by more than 1, it depends how fast you move the thumb.

BTW, it's a strange logic, don't you think so?
It would be great if somebody from Microsoft Team could comment this and explain which exactly logic is implemented inside the ScrollBar. Note that if you play with scrollable MS controls for WinForms (DataGridView etc), you will not see any "jumps" near the edges.
Comanche  Saturday, October 03, 2009 8:06 AM
Okay, I figured out what is going on.  The problem is using the scrollbar's Value property in the Scroll event.  It hasn't been updated yet, you must use e.NewValue instead.

Hans Passant.
nobugz  Sunday, October 04, 2009 5:08 AM
Thank you very much!
Meanwhile I've been enjoying development of a scrollbar "from scratch". I've just finished it when I saw your reply... well, now I have a choice how to solve the problem! BTW, can I post my ScrollBar control somewhere at this site? I think it may be useful to beginners...
Comanche  Sunday, October 04, 2009 1:41 PM
Use codeproject.com to show your stuff.

Hans Passant.
nobugz  Sunday, October 04, 2009 2:03 PM
Thank you.
The only feature I couldn't manage to implement is drawing my ScrollBar control with "XP Theme".
It's interesting that in Design mode it's drawn with XP Theme, but in run-time it has a "classic" appearance.
I cannot use Application.EnableVisualStyles() because my scrollbar is a custom control, created with CreateWindowEx.
I do not want to use manifest file because it must be distributed separately from the assembly and can be easily deleted.
I also cannot use UxTheme.dll because my control is not owner drawn really - it's just a wrapper over standard Win32 "ScrollBar" window.
Any ideas?!

PS: I also tried to use the technique described here: http://msdn.microsoft.com/en-us/library/ms649781%28VS.85%29.aspx. I copied manifest contents from this page, reformatted it in single line, and placed it into a resource named "RT_MANIFEST". Then added the InitCommonControls call to the ctor of my control and rebuilt the control. No effect. Am I doing smth wrong? or maybe this approach cannot be applied to .NET assemblies?!

PPS: Besides, I also used "Method #1" from here: http://blogs.msdn.com/cheller/archive/2006/08/24/718757.aspx. Also no effect. I embedded manifest into my Control Library (dll). Maybe it's a mistake and I should play with the host EXE?!
Comanche  Sunday, October 04, 2009 2:56 PM
The Control class automatically calls ActivateActCtx() when it creates the window handle, if visual styles are enabled.  Sounds like you didn't derive you custom scrollbar from Control.  Trying to fix this without deriving is painful and quite beyond the context of this forum.  Ask questions about this in the Windows SDK forum.

Hans Passant.
nobugz  Sunday, October 04, 2009 4:00 PM
I'm deriving from UserControl and calling CreateWindowEx in the constructor.
I also tried to derive from Control but rejected this because in such case the "ParentForm" property is missing (and I need to subscribe to the parent form's FormClosed event - to destroy "ScrollBar" window handle).

So I think there's some other reason.
Comanche  Sunday, October 04, 2009 4:28 PM
Don't call CreateWindowEx yourself, override CreateParams to set the arguments that Control will use to call CreateWindowEx.  Use Reflector to see how HScrollBar does it.

Hans Passant.
nobugz  Sunday, October 04, 2009 5:36 PM
For the moment I do not have Reflector at hand, so I tried to do it myself. Well... nothing is drawn on the UserControl's surface. That's my code:
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cParams = base.CreateParams;
                cParams.Caption = null;
                cParams.ClassName = "SCROLLBAR";
                cParams.ClassStyle = 0; // ignored anyway when ClassName is set
                int lStyle = (int)(Win32.WindowsStyles.WS_CHILD | Win32.WindowsStyles.WS_VISIBLE);
                lStyle |= (int)(_scrollBarOrientation == ScrollOrientation.VerticalScroll ? Win32.ScrollBarStyle.SBS_VERT : Win32.ScrollBarStyle.SBS_HORZ);
                cParams.Style = lStyle;
                cParams.ExStyle = (int)Win32.WindowsExStyles.WS_EX_NONE;
                int iWidth = this.Width;
                int iHeight = this.Height;
                if (this.GetScrollBarOrientation() == ScrollOrientation.VerticalScroll)
                {
                    iWidth = Win32.GetSystemMetrics(Win32.SM_CXVSCROLL);
                }
                else
                {
                    iHeight = Win32.GetSystemMetrics(Win32.SM_CYHSCROLL);
                }
                cParams.Width = iWidth;
                cParams.Height = iHeight;
                cParams.Parent = this.Handle;
                cParams.X = 0;
                cParams.Y = 0;
                return cParams;
            }
        }

Doesn't matter whether I derive my usercontrol from Control or UserControl.
The same parameters worked fine when I used them in CreateWindowEx call!
Comanche  Sunday, October 04, 2009 6:31 PM
OK, I downloaded Reflector, and that's what I see in ScrollBar CreateParams override:

protected override CreateParams CreateParams
{
    [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
    get
    {
        CreateParams createParams = base.CreateParams;
        createParams.ClassName = "SCROLLBAR";
        createParams.Style &= -8388609;
        return createParams;
    }
}

Inherited VScrollBar extends Style this way:

protected override CreateParams CreateParams
{
    [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
    get
    {
        CreateParams createParams = base.CreateParams;
        createParams.Style |= 1;
        return createParams;
    }
}
So, in my code I try the following:

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams createParams = base.CreateParams;
                createParams.ClassName = "SCROLLBAR";
                createParams.Style &= -8388609;
                createParams.Style |= 1;
                return createParams;
            }
        }
And it doesn't work! I mean that I still get nothing drawn at my usercontrol's surface.
Comanche  Sunday, October 04, 2009 6:54 PM
Sigh.  Use SetStyle to turn off UserPaint.

Hans Passant.
nobugz  Sunday, October 04, 2009 7:09 PM
Thank you, this helped! BTW, my first version of the CreateParams override had the following problem: cParams.Parent = this.Handle; - this line should be removed! XP styles are OK now. There's a strange behavior of the scrollbar's backcolor (on click), though.
Comanche  Monday, October 05, 2009 8:54 AM

You can use google to search for other answers

Custom Search

More Threads

• Minimize/Restore Form
• How do I map a VBA.Collection in .NET ???
• Application.Exit()
• Extending the Button Class
• Codedom assembly output type
• ObjectDisposedException problem
• items in top level of the Listview
• autocomplete function for textbox??
• How to compare the Dates in VB.net
• Why my textbox could not display text file correctly?