UserControl, child control, and scrolling

Greetings,

I have a custom user control.  Because I have a complex hierarchy of child object that can be drawn to the screen (potentially thousands of objects) I opted not to derive them all from Control/UserControl for various reasons.  Instead, they all derive from a custom class that has no base class (except object).

The UserControl has AutoScroll = True.  When I draw one such child control into the UserControl, if it is outside the bounds of the visible area, the scroll bars do not appear.

How can the UserControl become aware that there is come content that isn't visible, so it can show the scrollbars


Thanks,
Shawn


Answer this question

UserControl, child control, and scrolling

  • WPX

    Instead of drawing to the control draw to a bitmap which will also act as a doublebuffer.

    Create a bitmap of sufficient size to contain all your lite controls.
    draw your lite controls to the bitmap and set it as the backgroundimage of the usercontrol.
    set the usercontrols AutoScrollMinSize to the size of the bitmap.

    now in the mouseup/mousedown/mousemove events offset the mouseposition by -AutoScrollPosition to get the relative coordinates.

  • Clifford Grimm

    You can't just 'draw' your control to the container and utilize autoscroll.  If you're displaying your controls via painting, then it's up to you to handle scrolling in the "container".

    In order for this to work you need to add your controls to the Controls() collection of the contianer control.  Then the container will render your controls and be aware of their positions.  Now Autoscroll will work as expected.

    Of course, your controls will have to be controls...

  • A tawil

    Well then, to just answer your question:  you can't.  You can only draw on the visible portion of a control.
  • TenShekels

    Your original question was how to get AutoScroll to work with your control, and now you're back to my original answer:  <I>If you're displaying your controls via painting, then it's up to you to handle scrolling in the "container". </I>

    It's good that you figured it out on your own; you learn more than if someone just gives you an answer.

    You hit the main point when you said "I guess it wasn't AutoScroll that I was so concerned with as figuring out how to set the scrollbars myself".  The point I was making was that under your design, autoscroll was not going to work for you...  Now, Mick's idea... that's good stuff.

    Finally, my last post said that you can't draw outside the visible area of a control.  I suppose it would be better worded that there is no <i>point</i> to drawing outside of the visible area... why waste cycles drawing something that can't be seen

  • AlexU

    BTW: Bob Powell referred me to his article which actually has a better solution, I wasn't aware of the Transform mechanism for this.  (Like I said, I'm new to WinForms as I've been doign back end services or ASP.NET for the last 4 years (since v1 b1).

    http://www.bobpowell.net/understanding_autoscroll.htm

    Thanks,
    Shawn

  • ff_mac

    Every container control implements methods specificaly for finding and handling child controls...  Pass GetChildAtPoint() the point the mouse clicked in the container and it will return the child control clicked.  There's also GetNextControl(), Contains(), the Controls() collection... numerous ways to work with the controls in a container.

    The container is aware of the placement of it's child controls through the component model.  If you were to look closely at those 200,000 lines of code you may find that while the custom object you speak of inherits from System.Object, it implements enough interfaces to pass as a control.  Otherwise you're probably going to find some helper class or method that is measuring each drawn item and comparing it's bounds to the "container's" bounds and then adjusting the container's scrollbar properties accordingly.  It may amount to several overrides of the container's base class to make use of the AutoScroll property.

    Basically, what you have is a custom Object.  Not a user control.  A control is a component with a display element.  So to get a Control, the inheritance is System.Object -> System.MarshalByRefObject -> System.ComponentModel.Component -> System.Windows.Forms.Control.  Only the end result, a control, can be added to a container (a controls collection) and utilize the AutoScroll property under it's default implementation.

    If you want the CLR to work for you, you have to be willing to work with it.

    Sorry that doesn't directly answer your question.

  • Hennesey

    Okay, I solved this problem (I think) but I have another problem:

    In my OnPaint, in a custom UserControl, I have the following code:

    protected override void OnPaint(PaintEventArgs pe)
    {
       base.OnPaint(pe);
       ControlPaint.DrawBorder3D(pe.Graphics, 0, 0, Width, Height, Border);

       Graphics g = pe.Graphics;
       g.SetClip(pe.ClipRectangle);

       Rectangle rect = new Rectangle(10, 10, 10, 10);
       g.FillRectangle(new SolidBrush(Color.Black), rect);

       if (Controls.Count == 0) 
       {
          Button btn = new Button();
          btn.Top = 50;
          btn.Left = 50;

          Controls.Add(btn);
       }

       this.AutoScrollMinSize = new Size(1000,1000);
    }

    And in the Constructor:

    {
       InitializeComponent();

       Border = Border3DStyle.Sunken;

       SetStyle(
          ControlStyles.DoubleBuffer |
          ControlStyles.UserPaint |
          ControlStyles.AllPaintingInWmPaint |
          ControlStyles.ResizeRedraw |
          ControlStyles.Selectable |
          ControlStyles.ContainerControl,
          true
       );
    }


    When I scroll, the Rectangle in OnPaint that is set to 10, 10, 10, 10...
    always redraws at the 10, 10 (x, y) position in the control.  In otherwords,
    as I scroll, it is always in the same place even while if I have a TextBox
    for example, scrolls properly.


    Oh... I'm calling Invalidate() in the WndProc as follows:

    protected override void WndProc(ref Message m)
    {
       base.WndProc (ref m);

       int WM_HSCROLL = 0x114;
       int WM_VSCROLL = 0x115;
       
       if (m.HWnd != this.Handle) 
       {
          return;
       }

       switch (m.Msg)
       {
          case User32.WM_HSCROLL:
             Invalidate();
             break;

          case User32.WM_VSCROLL:
             Invalidate();
             break;

       }
    }


    What is wrong

    Thanks,
    Shawn

  • vidhyaprakash

    The problem is, if they are all controls, it got to be next to impossible for me to correctly track who is being clicked in, where the mouse is being dragged if I'm making a selection, and so on.  It was such a nightmare to get right...

    Anyway, somehow, the container knows whether their is some painting outside the visible display and adjusts accordingly, there must be a way for me to notify the container or take some control over that.  I know of at least one project that actually does that, the NetronProject, but I can't follow 200,000 lines of code to pinpoint the exact point where this takes place (he's also deriving everything form Object).

    Anyway, other approaches I've seen and heard of derive from Component, instead.  So it is possible to do this without everything being Control.  What I'm asking, is, if anyone knows what it is I'm looking for.  In the end, its probly on a few lines of code.

    But one thing for sure, I can't have everything be Control when there might be 20,000 objects, even when I have only 2,000 of these things, I may as well give up, getting anything to work properly is a realy pain and not worth the trouble.  I'd rather track it from a higher level, it is soooooooo much easier, except that I can't know when to draw scrollbars if something is off the screen.  Something tells me, its not a complicated problem to solve.  Someone, somewhere, has done it, and I'm hoping that person will read this email.


    Thanks,
    Shawn

  • DonRon1972

    In my previous post to this thread, I asked why, when I paint a rectangle at x=10, y=10, w=10, h=10... even when I am scrolled at the bottom (of a 1000 pixel container), it draws the rectangle at the 10,10,10,10 position/size on what is visible, rather than at the 10,10 position of the container (actually, this control is the container, so I fail to see why it is such a problem).

    In the example code above, I didn't use any custom objects, I just used a rectangle.  My question is, how do I make that rectangle draw at the x=10, y=10 coordinate, even if it is 900 pixels off-screen.  More specifically, if the top of the viewble area is pixel 850, and I draw a rectangle at x=10, y=10, I shouldn't expect to see the box on screen.

    Solving that problem will help me solve my more immediate problem.

    Anyway, taking a closer look at the code I keep referring too, his main Control actually derives from ScrollableControl and all the children objects that he draws or that we can interact with do not implement any interfaces or derive from MarshalByRefObject or any of its derivitives.  They are comlpetely custom objects.  His main Control site contains a custom controls collection which is nothing more than a CollectionBase, and his objects get added to that collection (instead of the Controls collection) and everything works fine.  I guess I'll just have to sift through the code to figure out why it works.  Like I said, I don't want 20,000 Hwnds in my form, not if I want it to perform well.  So in case it hasn't become obvious yet, what I'm doing isn't directly supported by the .NET framework because unless I want crap performance results and very difficult to solve technical issues (more difficult than this one), then I am not going to derive from Control (I have already been doing that and I'm not happy with the results).

    So my direct question is "how do I make that rectangle draw at the x=10, y=10 coordinate, even if it is 900 pixels off-screen.".  It is obvious that the Graphics object (from the pe argument in OnPaint) refers to the visible area and location 10,10 refers to the location 10,10 in view, not in reality.  How do I draw outside the area currently scrolled into view   That is my question.  Lets just forget that I'm not derving from Control for now.


    Thanks,
    Shawn

  • asifbawany

    Thanks mike, that's an interesting idea.

    rkimble, I solved the problem and it isn't that difficult.  Since we have a property telling us where the scroll position is, all I have to do is some math in my lite controls (as Mick puts it) and things seem to work okay.  In fact, its exactly what the guy in the other project is doing (I only realized it after I tried it on my own, then I understood what he was doing).  Concerning AutoScroll, I guess it wasn't AutoScroll that I was so concerned with as figuring out how to set the scrollbars myself.  Which, BTW: is a problem I'd still have to face if I was implementing the scrollbars myself outside of the ScrollableControl.  You say I can't, but I did.  I'm not the kind of person that takes "no" for an answer.  Everything is built on top of the Win32 API and so there is a way.  I wasn't asking for what I can't do, its already obvious.  I was asking for help on exactly what-to-do.  But since I didn't get any help, I figured it out anyway.


    Thanks everyone,
    Shawn

  • Vin Klassen

    Finally, my last post said that you can't draw outside the visible area of a control. I suppose it would be better worded that there is no point to drawing outside of the visible area... why waste cycles drawing something that can't be seen


    Actually, I worded myself very poorly.  When I say draw something at x,y = 10, 10, and the top visible pixel is 900, I don't expect to see something at 10, 10 (if the visible area).  The real issue is that I wasn't aware of a way to test whether or not I should be drawing.  Of course, there is the AutoScrollPosition (or whatever it is called) property that will actually say something like 0, -870 or something, knowing that, I can do some calculations and figure out whether I need to be draw or not.  More to the point, an object might be partially off screen, I'd rather just draw the entire object, even if it is a few pixels off-screen than to further complicate it by drying to figure out what portion actually *is* on screen... since my painting algorithms don't take all that into account and I'm not in the mood to change it.  It wouldn't be a performance kill to draw a few pixels off-screen until I complete the project and go back to optimize it.  There will be no signficant off-screen drawing as even my largest single visible object-container object fit onto the screen.

    Anyway, I like the Background image idea of Micks; I'll give it a try.


    Thanks,
    Shawn

  • UserControl, child control, and scrolling