Sunday, September 04, 2005

Is your AutoScroll too auto?

In general, .Net does a good job hiding the fact that Windows Forms is still based on Win32. That said, sometimes there are things you want to do and figuring out how to do it requires Win32 knowledge. One of my previous posts about mouse events and UserControls is an example of that. Joel Spolsky calls this a leaky abstraction. He says that:

And all this means that paradoxically, even as we have higher and higher
level programming tools with better and better abstractions, becoming a
proficient programmer is getting harder and harder.

I agree with the spirit of what he is saying but I will point out that our higher level programming tools are enabling us to be more and more productive for the 80% of the abstraction that doesn't leak and that as the tools become more mature, a smaller portion of the abstraction leaks.

Windows Forms embraces the fact that some of the abstractions are going to leak by supporting interoperability with Win32 not only through platform invoke (by nature of being .Net Framework based) as well as through the Windows Forms APIs like Control.WndProc().

AutoScroll
The main subject of this post is around the AutoScroll functionality implemented in the System.Windows.Forms.ScrollableControl class and how we can use the Windows Forms support for leaky abstractions in conjunction with that functionality.

Typically, you will not use the ScrollableControl class directly, however it is in the inheritance hierarchy for Panel, Form and UserControl.

The auto scroll functionality in the ScrollableControl is pretty cool. In the majority of cases, it does what you would want and it sure saves you a lot of time implementing scroll bar behavior.

Essentially, the ScrollableControl resizes its scroll bars based on the position and size of its child controls at runtime so as you resize the client area of the ScrollableControl the scroll bars will automatically resize and come and go as needed.

For example, if you have a simple Form with the following code in the constructor after the InitializeComponent() call:

AutoScroll = true;
ClientSize = new System.Drawing.Size(400, 300);
aButton.Location = new Point(500, 400);

Since the button is outside of the client area of the Form, you will get scroll bars. As you resize the Form, the scroll bars will adjust appropriately.

Scroll Events

How do you determine when the user is scrolling? In Whidbey there is a Scroll event. However in .Net 1.0 and 1.1, you had to take advantage of WndProc and knowledge of the underlying Win32 API and do something like this:

private const int WM_VSCROLL = 0x115;
private const int WM_HSCROLL = 0x114;

protected override void WndProc(ref Message m)
{
if
(m.Msg ==
WM_VSCROLL)
{
Trace.WriteLine("Vertical Scroll");
}
if
(m.Msg ==
WM_HSCROLL)
{
Trace.WriteLine("Horz
Scroll");
}
base.WndProc(ref
m);
}

Yes, this is a leaky abstraction but the nice thing is that there is support in Windows Forms to work with the underlying Win32 framework (it would've been nice if they also defined all of the Windows Message codes in the Framework since everyone ends up defining them) through WndProc.

Scrolling a Control into View
The ScrollableControl has a method called ScrollControlIntoView which will adjust the scroll bars such that the passed in control is in view.

That said, the AutoScroll has another behavior in that it will scroll a control into view when that control gains focus.

That's a reasonable assumption, but what do you do when you don't want a scroll to occur? For example, suppose you have a RichTextBox and a button side by side as below:
















Notice that "button1" has the focus. If you click on the RichTextBox to give it focus, the scroll bars will scroll until the upper left corner of the RichTextBox is in view.

But what if, for whatever reason, you wanted to keep button1 in view? How would you do that? Surely you could just set the AutoScrollPosition property right?

That depends, when would you set it? If you set it in one of the events related to the change of focus, it just doesn't work. The reason is because the AutoScroll behavior is occuring after your event handlers.

Admittedly, the "fix" to this is a bit of a hack but what you need to do is look beyond the abstraction and realize that you can work around this by resetting the AutoScrollPosition by posting a Windows message and setting it when that message is processed.

Posting and Handling a Windows Message
In order to post and handle a Windows Message, you could use Win32 mechanisms, or you can realize that the Control.BeginInvoke() mechanism provides a facility that does exactly that.

MSDN's description of Control.BeginInvoke() is:

Executes a delegate asynchronously on the thread that the control's
underlying handle was created on.

In other words, its a nice managed equivalent to PostMessage(). When calling Control.BeginInvoke(), Windows Forms will post a message to itself and the BeginInvoke() will return. When the message is processed, the delegate will be executed at that time.

For the example above, the code will look like this:

private void richTextBox1_Enter(object sender, System.EventArgs e)
{
Point p = panel1.AutoScrollPosition;
AutoScrollPositionDelegate del = new AutoScrollPositionDelegate(SetAutoScrollPosition);
Object[] args = { panel1, p };
BeginInvoke(del,args);
}
private void SetAutoScrollPosition(ScrollableControl sender, Point p)
{
p.X = Math.Abs(p.X);
p.Y = Math.Abs(p.Y);
sender.AutoScrollPosition = p;
}

Now when the focus changes from the button to the RichTextBox, the scroll position doesn't.

Note that using Control.BeginInvoke() to post and handle a Windows Message via a delegate is a very powerful and useful facility that can be used in many situations beyond this one. Again, this is a leaky abstraction but it sure is a lot easier to use Control.BeginInvoke() instead writing my own mechanism for posting a Windows message which when handled will call a delegate.

Finally
The AutoScroll functionality is useful, of course its important to look at your requirements and make the right decision around using it or opting to use ScrollBars instead.

In a system as complex as writing Windows applications, its not surprising that there are leaky abstractions. I'm just glad that there is first class support to work with the underlying system not only to solve problems but also to reuse legacy code.

I don't think leaky abstractions are dragging us down, I think they are a necessary evil that is a by product to progress. It highlights the importance of always understanding how things work, not just how to get things to work.

36 Comments:

At 7:18 AM, Anonymous JK said...

Thanks alot for this nice explanation on how to better take control over the AutoScroll. I'm studying to become a software developer, and I'm doing an assignment where I have to synchronice the scrolling of two UserControls (which hold two different views of the same content), and I've not been able to find anything usefull about this - until your blog, that is :)
I don't know if this is the best solution for my problem, but at least I got it to work satisfactorily for my use.

Best regards
JK

 
At 12:29 PM, Anonymous Pete said...

Question:

I have a TabControl onto which I dynamically place Sever UserControls. However with the TabControl (and TabPage) AutoScroll Property set to true, I am no longer getting the AutoScroll functionality.?? It works fine If I use a standard control like a Label or Button.

 
At 12:30 PM, Anonymous Anonymous said...

* I am dynmically placing "Several" UserControls.
This is a Winforms project btw.
Thanks for any Info!

 
At 10:31 PM, Anonymous Bin said...

Thanks a lot.I have face this problem and it is puzzling me for a long time. Thanks

 
At 2:38 AM, Blogger LYx said...

thx, it works. but it's not perfect. I can still see the panel sets the scrollpostion to 0,0 and then it was set back. so there is a jitter i see.

 
At 11:32 AM, Anonymous Anonymous said...

Just in case someone stumbles across this post searching for a way to disable the AutoScroll behavior of scrolling to the focused control, the cleanest solution is provided in .NET 2.0: There is an overrideable ScrollToControl method in the ScrollableControl now. Replace the call to the base class implementation to return DisplayRectangle.Location and problem solved.

 
At 5:57 PM, Anonymous Anonymous said...

To the anonymous above... way to go!! Your comment helped me finish 48 hours of misery.

Why it is nowhere to be found inside msdn is another question.

 
At 9:38 AM, Anonymous Anonymous said...

To the first Anonymous above, thanks a million.

I must have wasted at least a few days on this problem.

 
At 9:31 AM, Anonymous Dapuzz said...

Your post is really intersting, but Anonymous 's comment above rocks!

 
At 7:36 AM, Anonymous Anonymous said...

The 1st anonymous comment about overriding ScrollToControll rules :-)

 
At 12:31 PM, Blogger Jon Webb said...

Thanks a lot for this. Solved a nasty UI problem for me.

 
At 6:42 AM, Blogger Arjan van IJzendoorn said...

Thank you sooo much Mr. Anynomous who wrote "Replace the call to the base class implementation to return DisplayRectangle.Location and problem solved."

You fixed the most annoying problem in my application!

/cheer

 
At 3:28 AM, Anonymous Anonymous said...

I managed to turn off the "scroll into view" functionality all together using a subclass like:

Public Class ScrollPanel
Inherits Panel

Protected Overrides Function ScrollToControl(ByVal c As Control) As Point
Return Me.AutoScrollPosition
End Function
End Class

 
At 3:35 AM, Anonymous Anonymous said...

In C#, the
"return DisplayRectangle.Location" instruction didn't solve my problem. My scrollable control was a TabPage.

However, extending the TabPage class and overriding the mentioned method with
"return this.AutoScrollPosition;"
I managed to get my "AutoScroll too Auto" problem solved.

This blog was, nevertheless, a great starting point and the first Anonymous post really was a help.

 
At 5:22 AM, Blogger Mickael said...

thank you again for the first anonymou post .. and of course for the author of the article

 
At 5:21 AM, Anonymous Anonymous said...

Hi Jim

Can i get any solution for this problem autoscroll type.

see the link

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2959472&SiteID=1

I amnot getting solution for this .

Thanks

 
At 11:29 AM, Anonymous Anonymous said...

Wonderfull, i have searched so long for a solution. Great, thank you.

 
At 10:13 PM, Blogger cdgnfg said...

Joy in warcraft leveling living comes wow lvl from having wow lvl fine emotions,wow power level trusting them,power leveling giving them power leveling the freedom of wrath of the lich king power leveling a bird in the open.wlk power leveling Joy in living can age of conan gold never be assumed as a pose,or put on from guildwars gold the outside as a mask. People who have this joy don not need maple story mesos to talk about it; they radiate it. wow gold They just live out their joy and let wow power leveling it splash its sunlight and glow into other lives as naturally as bird sings.

 
At 11:23 PM, Anonymous Anonymous said...

thanks a lot for the solution..was tired searching for the solution since 2 days..

However i have another problem...the right doesnt seem to be working on the first go..once the control regains focus through right clicking, the context menu doesnt open on the first time..it opens only on right clicking the control again..Can you help me that..

Thanks in advance..

 
At 6:35 PM, Anonymous Anonymous said...

Wow, great research guys! Saved me god knows how much time!
:D

 
At 7:18 PM, Anonymous Lakshmi Narayanan B said...

Thanks a lot.It worked and was a great learning.

@Anonymous
Wow!! A great tip!! You ended my 3sleepless nights.

 
At 5:55 AM, Blogger dfous said...

Bon marche de Dofus Kamas.achat de dofus.le prix moins cher.nous vendons dofus,or de Wakfu. 24/7 appui-en-ligne et livraison rapide.Wakfu Kamas
china: chinaserving

 
At 12:55 PM, Anonymous Michael said...

Or just write this:

public class MyPanel : System.Windows.Forms.Panel
{
protected override System.Drawing.Point ScrollToControl(Control activeControl)
{
return DisplayRectangle.Location;
}
};

 
At 11:11 AM, Blogger Pinakin said...

Thanks for this post. The problem disappeared temporarily for me but it resurfaced when my controls are on a container (a user control that uses ParentControlDesigner). My container is 0x,0y,500w,3000h. The child control is a text box user control and implements the solution suggested. Interestingly the autoscrollposition do not change even after setting it in SetAutoScrollPosition. I moved the control out of the container and it started working. Please help; i am using .NET 1.1

 
At 12:48 AM, Anonymous Anonymous said...

Thanks a million to anonymous, one day on this problem, and i could have spent a lot of day on this without your wonderfull comment :)

 
At 11:48 AM, Blogger virstock.com said...

Dofus Kamas|Prix Moins Cher Dofus Kamas|Kamas par allopass|Dofus kamas audiotel|Dofus kamas par telephone sur Virstock.com

Meilleur prix dofus kamas stock de dofus kamas

Prix moins cher dofus kamas
vente dofus kamas sur www.virstock.com

Kamas par allopass sur dofus kamas

Dofus kamas par telephone sur dofus kamas

Dofus kamas audiotel sur dofus kamas

http://www.virstock.com/jsp/comments.jsp dofus kamas vente

http://www.virstock.com

 
At 7:43 AM, Blogger Kiima said...

Thanks Michael,
The guys, the custom panel fixed my issue.

 
At 10:39 AM, Blogger Nicole C said...

buy viagra
viagra online
generic viagra

 
At 8:38 PM, Blogger King Bayern Munich said...

About the wonderful, very pleased to see this article, learn some things, and view the text is recognized. Thank you for sharing. At the same time
i love Colorful Sign very much !

 
At 10:30 PM, Anonymous generic cialis 20mg said...

Hi, well be sensible, well-all described

 
At 12:39 AM, Blogger yooo said...

For those of you who are purchasing Titanium Jewelry or Titanium

Necklaces
, we realize that this is one of the most important purchasing decisions you will make. You have made the best choice in choosing one of our Cross Titanium Necklaces, Titanium

Pendants
or Titanium Rings, as they are constructed of the highest grade materials to ensure that your new Titanium Jewelry lasts forever. The designers we feature are chosen for their unique modern styling and lasting quality.

You can be confident in your purchase as each item comes with a no hassle 30 day return privilege.

 
At 7:45 AM, Blogger Daniel Brilho said...

This comment has been removed by the author.

 
At 7:47 AM, Blogger Daniel Brilho said...

Great article, Jim!

Still valid after many years... I see that you are a guy concerned about the DETAILS, like me.

Saved my day.

Thanks!

 
At 11:58 PM, Anonymous Stritch said...

Awesome. Overriding ScrollToControl() fixes it alright. Thanks.

 
At 1:32 AM, Anonymous Anonymous said...

scroll bar for panel

 
At 6:45 PM, Anonymous Anonymous said...

create scrolling labels in .net

 

Post a Comment

<< Home