Link to home
Start Free TrialLog in
Avatar of tlfeet
tlfeet

asked on

Redraw controls after Resizing Form is done?

Hi,

Probably been asked before ... and I know there is an answer out there ... just not able to find it (looking for wrong term?)

Anyway, have VB.NET Windows app and users can resize the main form.
And, of course, resizing the form requires resizing, re-positioning of controls.

The issue is when resizing by moving the handles (on border) or the grip (as opposed to hitting min or max buttons) - just a tiny bit of movement (1 px horizontal or vertical) triggers the resize event.

As there are a fair number of controls (some requiring a fair amount of code for redrawing) ... it becomes very slow to re-size the form.

e.g. as user changes width from say 800 to 600 ... that is 200 times the Resize event is triggered.

Is there a way to supress redrawing everything until the user is done resizing?  I am not sure ... but say on MouseUp? or such  .... as opposed to it redrawing everything for each little, tiny change in size?

Thanks,
Mike
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of tlfeet
tlfeet

ASKER

Hi Idle Mind,

Yes, I think I could generalize that, or specialize that ;-) , as the case maybe to work with my project.

I need to dig into the WndProc method a bit ... er a fair amount ... er, well seems quite dense ... off to pick my way through it ...
Avatar of tlfeet

ASKER

Ok, I'm stumped ...

Where does one find the constant values?

Yes, VS help and MSDN Library say
"Actual constant values can be found in the windows.h header file included in the Platform SDK (Core SDK) download, which is also available on MSDN."

Well, have download, and installed.
Even found windows.h file, but constant values ain't there - at least not those that might be of interest or use.

Did find references to Winuser.h ... and it appears to have the values ... but given documentation says I should find the values in windows.h but they are not, ... well sort of confused as to what is where.

Is there a handy spot the defines the constants, what they are/do and their values?

Mike
I wish a had a GOOD answer for that question.

Personally I browse MSDN online and find the message I need.  For example:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowmessages/wm_exitsizemove.asp

Then I come here to EE and do a search for that constant name.  It is pretty rare that I can't find the constants value here somewhere.  When I can't find it here, I google it along with "const".

I've never looked up the values in the C header files.

Sorry...
Avatar of tlfeet

ASKER

Hi Idle Mind,

Yes, I understand what you are saying ... but:

Can, does, will WndProc deal with, handle, have something to do with say "X" ?
 - "X" beings something going on with app or OS ...

Stumble around find out "Yes, WndProc does somehow interact with "X" - but what constant does it use?  What is its name?

If you don't know the name, can't search for it?

So, 2 parter really ... Looking to see what/if WndProc might deal with, handle, have something to do ... does it get/process a message for "X" and then ... find the name ... which one does not know beforehand ... else one would not be looking for the name.

I did not know WndProc would somehow catch/deal with Reszing ... so did not even bother searching for "something ... who knows what" in WndProc

But, then you clued me in ... OK, so next q one would have is "Gee I wonder what constant or constants are involved ... might ne of use?"  Again, as the one does not know the name(s) ... hard to search for them. ;-)

Anyway ... going to tak eyour code bit, above and see what happens ... worse that happens ... I crash the internet ... ;-)

Thanks,
Mike
I understand what your saying...

Unfortunately I'm a self-taught programmer from the school of hard-knocks.

The majority of my knowledge comes from figuring out what doesn't work and from observing other peoples code (as in EE for example).
Avatar of tlfeet

ASKER

Hi Idle Mind,

Tried your code, and several variations on it ...

You'll see why in a minute .... but set up following methods/events handlers on my Form

Private Sub Form1_Resize (....) Handles MyBase.Resize

Private Sub Form1_Paint (...) Handles MyBase.Paint

Private Sub Form1_Layout (...) Handles MyBase.Layout

Protected Overrides Sub WndProc(ByRef m as Message)
... as with yours above.

The problem?

WndProc is the last Method/Handle called during the process of resizing, redrawing, layout.

Windows App calls, goes through the Methods/Handlers in this order:
Resize
Layout
Paint

and last WndProc

Thus setting the SIZING flag in WndProc is too late.

When resizing the form, WndProc is last method called and it never gets back to Resize handle (or Paint, or Layout)

No, big deal per se ... I guess I hope ... I did have the code to control/trigger resizing various parts of the app in the Form's Resize event handler ... just going to have to move it to the WndProc method ... I guess.

Is there some reason I shouldn't do that?
This looks like it contains a pretty extensive list of constants.

http://de.pastebin.ca/23663

It doesn't say which is used for what, but a lot of the names give decent clues.  And that might give a more focussed starting point for further searches.  And it appears to be up to date.

Another source is this

http://www.mcfedries.com/books/vba2000unleashed/Win32API.txt

Although it is vba, rather than VB.NET, and somewhat old, again it might provide a starting point.  I don't mess much with Windows Messaging but I have a version of this file that was originally supplied with VB5 so is even older.  But I've been able to use some stuff from it directly, and other with only slight modification, under VB.NET

Roger
Oops, cross-post, I see.  And in view of the latest message, probably irrelevant.  Never mind.

Roger
Avatar of tlfeet

ASKER

Hi Roger ... thanks for the list.

Anyway, q boils down to this ...

Is there, would there be something wrong with (I mean besides the world ending ;-) )

By doing this:

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        Select Case m.Msg
            Case WM_ENTERSIZEMOVE
                Debug.WriteLine("WM_ENTERSIZEMOVE...")
           

            Case WM_EXITSIZEMOVE
                Debug.WriteLine("WM_EXITSIZEMOVE...")
               ... insert
                   all my resizing, moving code
               

        End Select

        MyBase.WndProc(m)
    End Sub

As opposed to setting the SIZING flag and trying to catch that in some normal VB.NET, windows event handler?

I suppose not?

Thanks,
Mike
Before you do that, try running this

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(292, 266)
        Me.Name = "Form1"
        Me.Text = "Form1"

    End Sub

#End Region

    Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Resize
        Static i As Integer
        Debug.WriteLine("resize " & i)
        i += 1
    End Sub

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Static i As Integer
        Debug.WriteLine("paint " & i)
        i += 1
    End Sub

    Private Sub Form1_Layout(ByVal sender As Object, ByVal e As System.Windows.Forms.LayoutEventArgs) Handles MyBase.Layout
        Static i As Integer
        Debug.WriteLine("layout " & i)
        i += 1
    End Sub

    Private Sub Form1_Move(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Move
        Static i As Integer
        Debug.WriteLine("move " & i)
        i += 1
    End Sub

End Class

For me, each of those events only fires once if the form is resized "by moving the handles (on border) or the grip (as opposed to hitting min or max buttons)" or if it is moved.  That is, the form's own event only seems to fire, anyway, when the Windows Message is WM_EXITSIZEMOVE.

So I wonder why, in your case, "as user changes width from say 800 to 600 ... that is 200 times the Resize event is triggered."  And whether, if that is indeed the case with you, making the alteration you propose will make any difference.

Roger
Avatar of tlfeet

ASKER

Hi Roger,

I tried that ... well not worried about moving, per se, at this point ... and the Resize, Layout and Paint events are triggerred several times as I resize (using handles, as opposed to Max, Restore, Min.

I am going to double check firing sequence though ... one more time.

And, worse than them being triggerred several times (afterall, could put in code to ignore all but last) is that the WndProc method is not hit until after Resize, Layout, Paint handlers.

Mike
The WndProc() is ALWAYS hit first.

You should get WM_ENTERSIZEMOVE, followed by a series of resize/layout/paint..., followed by WM_EXITSIZEMOVE.

You can handle it all from inside WndProc() but then you need to find the constants for WM_PAINT, etc.....
Avatar of tlfeet

ASKER

Hi again,

Here is ouput in from debug Output window:

Last line of usual loading/startup output:
'WhatIsBlue.exe': Loaded 'c:\windows\assembly\gac\system.xml\1.0.5000.0__b77a5c561934e089\system.xml.dll', No symbols loaded.

Counter: 1  Form1_Resize SIZING: False
Counter: 2  Form1_Resize IN SIZING FALSE  SIZING: False

'WhatIsBlue.exe': Loaded 'c:\windows\assembly\gac\microsoft.visualbasic\7.0.5000.0__b03f5f7f11d50a3a\microsoft.visualbasic.dll', No symbols loaded.

Counter: 3  Form1_Layout SIZING: False
Counter: 4  Form1_Resize SIZING: False
Counter: 5  Form1_Resize IN SIZING FALSE  SIZING: False
Counter: 6  Form1_Layout SIZING: False

'WhatIsBlue.exe': Loaded 'xuylumb1', No symbols loaded.
Counter: 7  Form1_Resize SIZING: False
Counter: 8  Form1_Resize IN SIZING FALSE  SIZING: False
Counter: 9  Form1_Resize SIZING: False
Counter: 10  Form1_Resize IN SIZING FALSE  SIZING: False

'WhatIsBlue.exe': Loaded 'r1sk8dpk', No symbols loaded.
Counter: 11  Form1_Resize SIZING: False
Counter: 12  Form1_Resize IN SIZING FALSE  SIZING: False
Counter: 13  Form1_Layout SIZING: False
Counter: 14  End MyBase.Load

'Have code in a MyBase.Load method, so slapped in an output line at the end of it.

Counter: 15  Form1_Layout SIZING: False
Counter: 16  Form1_Paint SIZING: False
Counter: 17  Form1_Paint SIZING: False

'This next line is where, really I started resizing ... using handles on form border
'I resized it once, in a smooth move ;-) to about 1 inch smaller on the screen, maybe a little more

Counter: 18  WM_ENTERSIZEMOVE SIZING: True
Counter: 19  Form1_Layout SIZING: True
Counter: 20  Form1_Resize SIZING: True
Counter: 21  Form1_Paint SIZING: True
Counter: 22  Form1_Layout SIZING: True
Counter: 23  Form1_Resize SIZING: True
Counter: 24  Form1_Paint SIZING: True

[...]

Counter: 82  Form1_Layout SIZING: True
Counter: 83  Form1_Resize SIZING: True
Counter: 84  Form1_Paint SIZING: True

Counter: 85  Form1_Layout SIZING: True
Counter: 86  Form1_Resize SIZING: True
Counter: 87  Form1_Paint SIZING: True

Counter: 107  WM_EXITSIZEMOVE SIZING: False

The last line, being where I stopped resizing, let go of boder resize handle.
Avatar of tlfeet

ASKER

The last output line above, should read:

Counter 88: WM_EXITSIZEMOVE SIZING: False
Your output confirms what I said...

The initial Layout/Resize hits are from the form loading.

When you actually resized with the mouse, WndProc() was hit first and last.  Furthermore, during the resizing, the Sizing variable was true in the Layout/Resize/Paint events.
Avatar of tlfeet

ASKER

Just got your post Idle Mind;

Re:
"The WndProc() is ALWAYS hit first.

You should get WM_ENTERSIZEMOVE, followed by a series of resize/layout/paint..., followed by WM_EXITSIZEMOVE.

You can handle it all from inside WndProc() but then you need to find the constants for WM_PAINT, etc....."

As you can see, in my case yes, the WM_ENTERMOVESIZE is hit first, then it does the whole bunch of resize, pain, layout calls, then when I stop resizing it then hits WndProc one more time for WM_EXITSIZEMOVE

But, for the "thing" to work, once stopping reszing I need it to hit one (or more) of the Resize (best), Paint or Layout handles one more time.

So, yes, WndProc does get hit first - sets flag to true
Hits Resize handle several times as I resize.
During those resizing, do not want it to actually go through all the code to repostion/resize parts (as well, that puts me back to the beginning ... it owuld go through all that code several, dozens, hundreds of times during resizing)

Want to to go through the code when user is done resizing ... which occurs at the point it hits WM_EXITMOVESIZE

So, something "after" WM_EXITMOVESIZE needs to be the trigger for my code to reposition/resize my parts.

Indeed, surprised MS VB.NET does not have a Handle MyBase.DoneResizing or similar for DoneDidFinishedMoving

Sure explains why a lot of apps are funky if moving it/stuff about or resizing manually ...

Right now ... I do not have all the repositioning/resizing code hooked up (it is all not being called) so right now users' would not notice a slow-down or weirdness ... but thinking ahead to when I do get it all hooked up ...
... resize by one pixel, wait a half-hour while it redraws ... resize the next pixel ... wait another half-hour .... ;-)
Issue a refresh command after setting SIZING to false:

            Case WM_EXITSIZEMOVE
                Debug.WriteLine("WM_EXITSIZEMOVE...")
                SIZING = False
                Me.Refresh()
Avatar of tlfeet

ASKER

Hi Idle Mind,

Re: Me.Refresh()

I think I am going to need to setup a delegate for that (or something)

A little lost on the Method.

When, during, after Resizing, my Main Form is not the issue, or problem.

I have User Controls, classes, etc .... parts if you will and they are the ones that have to have their sizes, contents, bounds, positions, etc. etc. recalculated, redrawn, and resized.

e.g. I have a Label, part of a user control ... when form loads, it calculates how big to make the usercontrol and its label (and other bits) and for the label, it calculates how/what to display ... based in part on on the size allocated/available for the label.

Similarly, I have, an imaage gallery of a sorts, and say the whole gallery contains 50 images ... and the app is sized ... to say display 10 images per "page"  then user resizes app ... say smaller, then it may only be able to display 5 images per "page", so it must re-page the gallery and relayout the images based upon the new size.

Anyway, calling Me.Refresh does a fine job of doing the Main form, but does nothing about all the bits, parts "attached" to the form ...

I did some reading on the Refresh method ... looks like another one of those things that are going to require a bit of study, error, error, trial and error ... ;-)

I'll be Bach

Mike
The Refresh() call is supposed to be propogated down to all the controls on the form causing them to redraw as well.

You could try calling Invalidate() before refresh:

            Case WM_EXITSIZEMOVE
                Debug.WriteLine("WM_EXITSIZEMOVE...")
                SIZING = False
                Me.Invalidate()
                Me.Refresh() ' or use Me.Update()

If that doesn't work then we could always use a recursive routine to visit all the controls on your form and tell each one to repaint itself using the Refresh() method (or a combination of Invalidate()/Refresh()).
Avatar of tlfeet

ASKER

Hi Idle Mind,

Re: "The Refresh() call is supposed to be propogated down to all the controls on the form causing them to redraw as well."

Well, that is the thing ... the stuff, bits, parts aren't "controls" ON the form, per se, directly.

e.g. take my label, ... please

I have a UserControl, MyUserControl, which is on the form.
The Control as a label, MyLabel

So, yes, MyUserControl and MyLabel do get redrawn, regenerated, but they do NOT:
1. get resized, nor
2. does the content of the label change

By calling Me.Refresh on the main form.

What happens, absent specfici code to handle resizing MyUserControl and MyLabel is:

say I am reszing, by making the app narrower ... as I make the app narrower, the right hand edges of MyUserControl and MyLabel get chopped off.

And of course the contents of MyLabel do not change.

Similarly if I make app wider ... then well, if wide enough there is then a gap between the right edge of MyUserControl and the right edge of the app window.

But I need MyUserControl to fill the width of the app ... so it needs to have its width change along with the size of the app's width.
In addition, the content of MyLabel needs to change as the width of the app, hence, the width of MyUserControl, hence, the width of MyLabel and its text need to change in a corresponding manner.

Now, if all MyUserControl had on it was MyLabel, well, not much point of doing it that way ... so it does have more "stuff" on it.

But in addition, essentially what I am doing is pulling everything, out of, away from the main Form directly.
I am doing a re-write, if you will

I had MyLabel directly on the Form ... along with the code to change the contents.

If my Main Form only had that ... well I would not be doing the re-write, but my app literally has/had thousands of controls on it ... and having all the other code for them, also directly on the Form, worked to be sure

But its was a nightmare, picking through thousands of controls, their various methods, etc. all sitting on the Main Form ... "Let's see, where did I stick the handler for TextChanged on Label90123?"  ... "Where did I stick the code to recalculate number of images per page?  ... Oh, yea that's right it calls this Method, then that Method, then this other one to do its thing ...?"

I am sure if I had my main form very well organized, sectioned, regioned out, it would not be such a hassle ... but I didn't (or rather I tired, but as I added stuff, took stuff off, rearranged this part for one control, well ended up with tons of extraneous code, disorganized parts, etc. etc.)

Smart or not, right or not ... decided to pull almost everything out of the main Form itself, create User Controls and classes for the stuff, and for a given control/class, but all its methods, handles, etc. together in the UserControl (some are UserControls, consisting of other UserControls, which in turn are made up of other UserControls) ... and the main Form, basically just provides the means to hook them together and display them.

Long winded way of saying:

Yes, Refresh would work fine (for most part) if the stuff I was trying to deal with was/were controls directly on the form itself ... but they aren't.

But, your ideas and Roger's have been a big help and I think I have puzzled it out .... and will post back with what I came up with, shortly

Thanks a ton,

Mike
So are you manually resizing the usercontrols from within your Resize event on your form based on the current size of the app?

You can call those events directly after the WM_EXITSIZEMOVE messgage:

           Case WM_EXITSIZEMOVE
                Debug.WriteLine("WM_EXITSIZEMOVE...")
                SIZING = False
                Me.Refresh()
                Me.Form1_Resize(Nothing, Nothing) ' replace with the name of your sub that handles this event...

Avatar of tlfeet

ASKER

Hi again Mind,

Re: "So are you manually resizing the usercontrols from within your Resize event on your form based on the current size of the app?"

Sort of ... similar ... working out a few things ... as the resizing of one of my "things" can trigger the need to resize another one of my "things" ... which triggers the resizing of yet another "thing" ... wanna make sure ... what needs to be resized is resized, what does not need to be resized, does not ... and avoiding some sort of loop, where A->resizes B-> which resizes C-> ... etc. etc.   which triggers the resizing of A ... leading to some sort of never ending resizing loop ;-).

And need to work out some of the "when" issues ... e.g. if a "thing" is not visible ... do I need to, our should I, resize it anyway ... or wait until/if it become visible again to bother.

But that all means, I may need to get at some resizing Methods individually, out-of-the-mainstream, sort of thing, as well as being called from within WndProc or Me.Refresh or not, both, either, or .... more of just matter of picking through the logic of what, when, how, if ...

Mike
Avatar of tlfeet

ASKER

Hi again,

What I ended up doing?

My code
Form has usual Methods, for which I just do opening line/outline

Public Class Form1
     Inherits System.Windows.Forms.Form

     Private SIZING As Boolean = False
     Private WM_ENTERSIZEMOVE As Integer = &H231   ' right now, not actually using this
     Private WM_EXITSIZEMOVE As Integer = &H232
     Private WM_SIZE As Integer = &H5

#Region " Windows Form Designer generated code "
Public Sub New()
      MyBase.New()

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

Private components As System.ComponentModel.IContainer
[etc ...]

<System.Diagnostics.DebuggingStepThrough()> Private Sub Initialize Component()
[etc ...]

#End Region

I added:

Private Overloads Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

     'actually added this sub for reasons not directly related to this question
     'namely to handle loading in saved window size from an xml file, from last time app was quit
     'if I had code to apply that size to in MyBase.New, after "Add any initialization ...."
     ' the app/window would actually render with some "random" size and position ... wierd ... so moved calling
    ' calling that code into this method.
   '  
   ' works out well with this question
   
   code to load the xml file and apply settings, preferences, saved values, etc. stored in the xml file

    'added this to do the resizing thing

     SIZING = False   'see below, but the initial loading, start of app, resets from False to True and need it to be
                              'False at this point

     RefreshMyStuff()
End Sub    'Form1_Load

Protected Overrides Sub WndProc(ByRef m As Message)
     Select Case (m.Msg)

          Case WM_EXITSIZEMOVE
               If (SIZING) Then Me.Refresh()
               SIZING = False

          Case WM_SIZE
               SIZING = True
     ' working with WM_SIZE and not WM_ENTERSIZEMOVE as WM_ENTERSIZEMOVE is triggered with both
     ' moving and resizing the Form ... and issue is only when I need to resize things/stuff and do not need, nor
     ' want to trigger resizing code if the form is simply moved.
     '  Right now, don't see how, if moving owuld create an issue - but if it does, well I'll burn that bridge when I cross it. ;-)

     End Select
     MyBase.WndProc(m)
End Sub    'WndProc

Protected Overloads Sub Refresh()
     '  similar to WndProc ... overload the base Method with own method
    MyBase.Refresh()
    '    need to call the Base class refresh method so the form itself is still refreshed

    RefreshMyStuff()
End Sub     'Refresh()

Private Sub RefreshMyStuff()
      'my code to handle resizing, positioning and content of my "stuff" user controls, their controls, classes, etc.

      MyGallery.Repage()
      MyLabel.ResizeContent()
      MyListBox.ReloadContents()

      etc.

End    'RefreshMyStuff()

The Sub New() and Form1_Load methods, automatically construct, refresh, the apps main window/form - so at that pont do not need to do a Form1.Refresh()

But do need to RefreshMyStuff() ' partially as the loading of stuff from xml changes what/how/behavior of my stuff, and need to "initialize" them, and thus redraw them to match the size of main app/form.

After loading app/form and doing intial rendering, user could, may resize the form, so the WndProc WM_SIZING part catches that resizing has started/is going on.

Once user is done resizing then WndProc() with the WM_EXITSIZEMOVE catches the end of sizing (and moving for that matter), and if it was sizing that was happening (as opposed to moving) then need to Refresh form and then resize, position and/or change content of various stuff. ;-)

I've tested it out, several times ... with several parts of MyStuff hooked in and it seems to work well ( ... er well, well I actually do some math and logic properly ;-) in my methods)

Does not seem to throw any fatal errors, get stuck in some sort of loop ... and best of all, allows me to:
1. move a huge amount of code off the main form -
2. even eliminate large chuncks
3. and better seperate it all out - so I can work with bits, parts, pices of my stuff w/o my intermediate work/re-working mucking up something else.

By judicious use of docking and/or anchoring of controls, on UserControlA, which in turn is anchored/docked on UserControlB, ... etc. etc. which finally ends up on the form ... a lot of it does end up being auotmatically handled anyway.

It is just that some of it can't be handled via docking or anchoring (or I do not like how those automatic "type" features do it, so well now can write in some code to handle what needs to be handled.

Thanks Roger and Idle Mind