Link to home
Start Free TrialLog in
Avatar of ramses
ramses

asked on

Usercontrol Question+

Is there a BULLET-PROOF way prevent changing a UserControl's Size (width&height) in VB5CCE ?
Avatar of SirNick
SirNick

Hi ramses

Can't you use:

usercontrol.height = usercontrol.picture.height
usercontrol.width = usercontrol.picture.width

or have I mis-understood what you are trying to do.
Avatar of ramses

ASKER

Yes indead SirNick you have misunderstood.


It would be darn stupid to ask a question as how to set width or height of a control.  Any programmer would be ashamed to do so.

To be more clear about this matter I suggest you try the following:

Start VB -> Select Control Group from the New Project Group --> In the UserControls Resize event, add the following code:

With UserControl
  .Width=750
  .Height=750
End With

Close the UserControl and open Form1 from Project2.  Add the Usercontrol to that form and resize it with your mouse.  You will see that when resized in length or height the code is fired and the size back set to 750x750.  try resizing diagonaly and you'll see that for the 4 corners, only 1 corner triggers the Resize Event, and THAT'S what I would like a solution to


Ramses
You don't need to state Usercontrol just use...

Public Property Set Height(byval newHeight as Long)
  Resize
End Property
ramses, when I follow your instructions, everything works just fine.  Check your other event procedures, like Paint, to see if somehow something is conflicting.  Otherwise, i suspect you have a larger problem.  Also, try repeating your own instructions on a new project.

BTW, I don't have a Control Group option on my system, so I simply started a new project and added an ActiveX Control, added the code you specified, added a new project, dropped the component onto the new form, and everything worked fine.
Avatar of ramses

ASKER

Thing is, I haven't added any other routines yet.  The only code in my control is the one I stated.

Dave, If you look at my question, you'll see see that I already have the Property Set Height included.

The thing is that The RESIZE EVENT is not ALWAYS fired.

rspahitz, try resizing the dropped control in every direction.  You'll see that when resized in some direction, the size you set is kept instead that it is put back to 750x750

Just scream if you need more info

Ramses
Yup, dragged all 8 handles and all work fine.
Try recreating the project.
Avatar of ramses

ASKER

The Size actually kept at 750x750?  You used my code?  Which version of VB do U use?  I've been doing this with VB5CCE.  If any1 has that still installed, please take a look at this



Ramses
Ahhh.. Maybe the version.  I'm using VB6SP3.  I only played with VB5CCE once and never got that far with it...
Avatar of ramses

ASKER

Since any version of VB will cost me at least $100 (for learning edition) to $900 (professional), I'm stuck with the free VB5CCE.  You see...

Anyway, I already thought It might be something like that, but wasn't sure.  That's why I downloaded the latest service pack, had it installed manualy (no other way with VB5CCE) and still... it doesn't fix it

Anyway, it doesn't bother me that much to be stuck with not being able to make dll's or exe's.  Just as long as I can use MSIE to run my controls, everything's fine.  


Maybe, I can reprashe my question:


Is there a BULLET-PROOF way prevent changing a UserControl's Size (width&height)?
I'm not sure, but you could try putting the code in the Paint event procedure.  Not ideal, but since it's small code, it should be okay.

Avatar of ramses

ASKER

In my paint event procedure, I already go like this:

UserControl_Resize


See?
So it appears that the "bug" goes beyond just Resize...

You could try including a timer control to force the size every second, but then it would start getting silly...
Avatar of ramses

ASKER

I agree.

I remember you can use some API call to limit a form's height and with, but does it also apply for usercontrols?  If so, can U repeat the code for that API please?
I'm not familiar with that API.

But how about if you make a transparent background on the U.C. and then drop all of your components into a borderless 750x750 frame on the U.C.?  Since the frame would never resize, and the U.C. would have a transparent background, even if the U.C. got big, the user would only see what you want.
Avatar of ramses

ASKER

Add a CommonDialog Control to your project and resize it anyway you want.  You'll see that it doesn't allow it.

Having a transparant background without border is not what I want, dough


Ramses
Of course!  But given the restriction of version number, it might be a workaround until you can upgrade.  Meanwhile, you can always leave the border on the frame if you like, or add a picturebox and drop a graphic on it to appear as any type of frame you want.
Avatar of ramses

ASKER

Everyone.. thanks for your comments on this matter.

I would like a reaction from each and everyone who commented on this question as to wheter or not he/she agrees that this question will be deleted (as in no points being awarded) because I have not received "The" solution.


I will follow the wish of the majoroty.  Meanwhile I set a max wait time of 2 days.  What I mean with that is if after 2 days, not everyone answered, I will count the votes I got.  I believe this is faire and square democratic principle.


Ramses
ASKER CERTIFIED SOLUTION
Avatar of rspahitz
rspahitz
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 ramses

ASKER

Rspahitz, I think your suggestion is even better than what I proposed.  But to be fair I have to wait 4 the others to reply because otherwise they might feel like, you know, cheated out of points.


Kind regards


Ramses
Hi,

I read the whole thread and here are some thoughts. Hopefully before the question is downgraded to 0 points.

I do not have VB5CCE anymore, so I assume you are just right when you state it's a bug. As a matter of fact, see MSDN article Q171478 which describes exactly the bug you are having. It also says that VS97/sp2 fixes the bug. But you said you tried that and it didn't help you out. So here follows a workaround.

In MSDN article Q185733 you'll find a complete description on how to prevent resizing of a form using API functions. Every UserControl has a hWnd property (also in CCE I hope) and if not, you can get it from the hDC property. This means that you can simply rewrite that code to support a usercontrol, just by pasting it in your UserControl code window. The only things to alter are the lines where specific features of the forms are used. Like Form_Unload and Form_Load. You can place that code in the UserControl_Terminate and UserControl_Show event respectively.

Hope this helps,
Regards,
Abel

good idea, rspahitz. I have refunded all points to ramses, but you'll get the "A" for your help :-)

modder
Community Support
Ok, I see, just a few seconds to late :(

But anyway, you can still use the solution of course :)
Hi Abel. Sorry. That was very unfortunate timing. No worries though, if ramses feels that your suggestion is worth some points (s)he can just post a comment here and I'll help out.
Avatar of ramses

ASKER

Hi Abel

I'll give it a go, and, if you're right, you'll get the 50pts.  Just look out for a "Points for Abel" question in the Visual Basic Topic Area.

If you're not right, then I'll post comments here



Ramses
Avatar of ramses

ASKER

Abel


I've tried it, but allas it doesn't work.  When debugging I see that the procedure is called several times but thing is, I believe that a usercontrol is not considered as a "real" window.  Other thing is that it doesn't have a Load event, so I've put the "Hook" procedure call in the Show event.

Furthermore, my control doesn't have any visible controls at runtime (just at designtime).  I appreciate your help, but it didn't work.

Should you have any other ideas, just let me know and I'll try them


Ramses
You are right. Although it really is a real window (honestly! Windows just doesn't give you nor VB any choice here), it doesn't send a WM_GETMINMAXINFO message. But that's alright, because we just alter the function to handle the WM_WINDOWPOSCHANGED message, as follows:

Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Select Case uMsg
    Case WM_WINDOWPOSCHANGED
        Debug.Print "Yep, min/max"
        gUserCtl.Height = 200
        gUserCtl.Width = 200
        WindowProc = DefWindowProc(hw, uMsg, wParam, lParam)
    Case Else
        WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
    End Select
End Function


Also, we add these declarations in the general declarations section:

Public gUserCtl As UserControl1  'This should be the name of your UC

And we remove the declarations for:
- CopyMemoryToMinMaxInfo
- CopyMemoryFromMinMaxInfo
- MINMAXINFO
- POINTAPI
- WM_GETMINMAXINFO
although leaving them there won't hurt a bit.

Next, add a Height and a Width function to your usercontrol, like this:


Friend Property Let Height(newHeight As Long)
    UserControl.Height = newHeight
End Property

Friend Property Get Height() As Long
    Height = UserControl.Height
End Property

Friend Property Let Width(newWidth As Long)
    UserControl.Width = newWidth
End Property

Friend Property Get Width() As Long
    Width = UserControl.Width
End Property

And last but not least, alter the initializing function as follows (as far as I can see, you won't need it for the other two initialize events):

Private Sub UserControl_InitProperties()
    gHW = UserControl.hwnd
    Set gUserCtl = Me
    Hook
End Sub

Private Sub UserControl_Show()
    gHW = UserControl.hwnd
    Set gUserCtl = Me
    Hook
End Sub


On my system, this works fine and, as you requested "bulletproof". There's one catch though, that you ought to be aware of: subclassing is not this easy. Depending on how you deploy your project, you'll have to do more to make it "bulletproof". The first thing you will encounter is when you are working with more than one of your controls in one project. You may see some unexpected results. But since this is a 50pts question, I'll leave that for you to find out. It shouldn't be that hard once you have this going.

A hint, create a list or a library with matching usercontrols <-> hWnd pairs and search the right UC in the callback function.

Success!

Abel
Sorry, forgot something. Also add this to the declarations section of the module:

Public Const WM_WINDOWPOSCHANGED = &H47
Avatar of ramses

ASKER

OK, thanks.  Please give me a few days to figure this out since I'm currently staying a few days with my girlfriend and don't have much time to program now :-)


Ramses
Avatar of ramses

ASKER

Wooow!

When I tried it, I got SOO many "Out of Stack Space" errors after each other until VB crashes!


Do you have any explanation for this?  I've just created a blank usercontrol, added the code suggested by you and microsoft and dumped the control on a form.


Ramses
That could happen in several cases:
1. You pasted the code incorrectly
2. You altered it to resize to a certain percentage or other size that is not fixed.
3. A difference between your version and mine.

Additions for 2 and 3:
Stack overflows are most commonly caused by recursive functions (ie. functions that call themselves). Although this doesn't appear to be one, you do resize the window in the message-handler, which causes a new message to be sent to the window which again traps the message-handler which again resizes the window and so fort. With VB6 no resize-message is sent when the size of the window is already of the size you want it to be, but maybe on VB5 and CCE it is sent even when no actual resizing is done.

To avoid this, try the following:

Function WindowProc(...) As Long
   Static RecursiveMsg As Boolean
   Select Case uMsg
   Case WM_WINDOWPOSCHANGED
       If Not RecursiveMsg Then
           RecursiveMsg = True
           Debug.Print "Yep, min/max"
           gUserCtl.Height = 200
           gUserCtl.Width = 200
           RecursiveMsg = False
       End If
       WindowProc = DefWindowProc(hw, uMsg, wParam, lParam)
   Case Else
       WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
   End Select
End Function


This should at least avoid the stack-overflow, even if the stack-overflow is due to different functionality between VB5 and VB6.

Some things to note when working with callback functions:
- Always save your code before you run it
- Never break in a callback function, unless you really know what you're doing. Chances on crashing VB are great.
- Don't be surprised when VB crashes or vanishes without warning. You run in the same address space as VB, when you create a system fault by messing with callbacks, VB cannot do anything but crash.
- Use Debug.Print statements extensively.

Hope this helps,
Abel
Avatar of ramses

ASKER

I'll give it another go...
Avatar of ramses

ASKER

Now, I get an error 91 in the following line:

    ...
--> gUserCtl.Height = 200
    gUserCtl.Width = 200
    ...

Object variable or With block variable not set

This usually means that you never assigned the control to an instantiated object.

Did you place the object on the form (named gUserCtl)?

Or did you otherwise cerate the object:

Dim gUserCtl as MyObject
set gUserCtl = New MyObject
?
In addition to rspahitz, from my code above, these events should be implemented and as follows:

Private Sub UserControl_InitProperties()
   gHW = UserControl.hwnd
   Set gUserCtl = Me
   Hook
End Sub

Private Sub UserControl_Show()
   gHW = UserControl.hwnd
   Set gUserCtl = Me
   Hook
End Sub

In both events, the second line in the event procedure makes that gUserCtl is set correctly BEFORE it is hooked (third line). This second line is probably incorrect, placed in the wrong procedure or not copied at all. It must appear in your usercontrol code, not in the module code.

Rgds,
Abel
PS, the "New" statement by rspahitz should better not be used, it will make the code unpredictable (you create a new instance of your object/control) or it won't work at all. Usually you don't want to create a VB-control with "New".
abel, you're right.  I didn't refer back to the original code, and commented out-of-context.
Avatar of ramses

ASKER

I'll give it a go... hold on
Avatar of ramses

ASKER

Hello all

Finnaly, I got the code to work without crashing vb.  I had to move some functions to the usercontrol and instead of the module, but now it works.  

The only thing is, it has the same flaws as method I provided:

Private Sub UserControl_Change
  With UserControl
    .Width=200
    .Height=200
  End With
End Sub

It does limit the height/width of the userobject, but only on some occasions.  When dragged some of the handles (SW for example), the window height is not limited and set to the width/height the user dragged.

I appreciate your efforts guys, but this is NOT bulletproof.


Ramses
Probably the window's sizing's messages aren't sent or aren't sent correctly.

Here's what you do to make it bulletproof:
In the Case Else statement, make it look like this:

   Case Else
      Debug.Print "Msg = " & uMsg
      WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)

Now when you create a new control (always delete the old one and create a new control when you alter code that is called in the InitProperties event!) you'll probably see a lot af statements in the immediate window. Let them be. Now, change the usercontrol in the way that it didn't correctly resized and at the same time that you hold the mouse-pointer for resizing, look at the messages that appear in the immediate window. If you don't see any, we'll talk further on that later, but you probably see some in a repeating fasion. The ones that are repeated over and over when you move your mouse while resizing should be noted. Post them here and I'll give you a solution that will work either way.

Abel
Avatar of ramses

ASKER

ok, thanks for the suggestion.  I'll get to that when I get up, 'cos it's time for me to hit the sac now (it's 12:55am here)

Anyway, thanks, and I will post as soon as I have more information.



Ramses
Avatar of ramses

ASKER

Please note that I have changed the code a bit to compensate with the debug.print statement (otherwise the hook doesn't work fast enough to finish processing before a new message is received)

In the WindowProc Function:

Static OldMsg As Long
...
...
...
Case Else
  If uMsg <> OldMsg Then
    Debug.Print "Message called: " + Trim(Str(uMsg))
    OldMsg = uMsg
  End If
End Select
...

So, if the same message gets sent twice after each other only the first message is shown in the debug window

Here is the output from the debug window:

Please note that, while the "Yep Min/Max" comes up on every resize I do, the size is not always adjusted.  Other thing: while dragging then handles, no messages are being sent (but I guess that it is ment to be like that)

WM_WINDOWPOSCHANGED=&H47 (71)
WM_GETMINMAXINFO=&H24 (36)


Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 33
Message called: 32
Message called: 70
Yep, min/max
Message called: 3
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 3
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 3
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 3
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 3
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 3
Message called: 5
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 132
Message called: 32
Message called: 132
Message called: 33
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 33
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 132
Message called: 33
Message called: 32
Message called: 70
Yep, min/max
Message called: 3
Message called: 132
Message called: 32
Message called: 132
Message called: 32
Message called: 70
Message called: 131
Message called: 133
Message called: 20
Yep, min/max
Message called: 3
Message called: 5
Message called: 70
Message called: 15
Message called: 20
Message called: 131
Message called: 133
Message called: 20
Message called: 5
Message called: 70
Message called: 15
Message called: 20


Ramses (btw: don't you have a list of what each message number means)
> (btw: don't you have a list of what each message number means)
As a matter of fact I have. And you too, on msdn.microsoft.com. But that doesn't show the numbers, only the constants. For the numbers, you can use your API viewer that came with VB, but for your convenience, I already looked them up:

3     &h0003     WM_MOVE
5     &h0005     WM_SIZE
15     &h000F     WM_PAINT
20     &h0014     WM_ERASEBKGND
32     &h0020     WM_SETCURSOR
33     &h0021     WM_MOUSEACTIVATE
70     &h0046     WM_WINDOWPOSCHANGING
131     &h0083     WM_NCCALCSIZE
132     &h0084     WM_NCHITTEST
133     &h0085     WM_NCPAINT

The only thing to alter in the code to try to get things working is this:

    Select Case uMsg
      Case WM_WINDOWPOSCHANGED, WM_SIZE

you just add the WM_SIZE message in the case-statement and keep the rest intact. See what happens.


> while dragging then handles, no messages are
> being sent (but I guess that it is ment to be like that)
You are correct, I was mistaken there. It is the same in all versions of VB: no messages while dragging a handle of the control on a form.

Abel
ramses, given that you are continuing to ask questions on a zero-point, closed question, I recommend you open a new question for abel so he can get points for his efforts.  After you open that question, add a reference as a comment here.


--
Note: When you grab the handles of a Window, it will trigger the resize event for every mousemove IF Windows is configured that way.  In Win2000, I think this is set through Display properties, Effects, "Show window contents while dragging."  You can verify it with this code:

Private Sub Form_Resize()
  Static Cntr As Integer
  Cntr = Cntr + 1
  Debug.Print Cntr;
End Sub
Avatar of ramses

ASKER

Everyone

I've started a new thread here

https://www.experts-exchange.com/jsp/qManageQuestion.jsp?ta=visualbasic&qid=20179243

Please note that I have currently only 10 points available but I'll increase as soon as I have more


Ramses
If you're enhancing an existing control, that single constituent control will typically occupy the entire visible surface of your UserControl object. You can accomplish this by using the Move method of the constituent control in the Resize event of the UserControl, as shown below.

Private Sub UserControl_Resize()
   picBase.Move 0, 0, ScaleWidth, ScaleHeight
End Property

The code above assumes that an enhanced picture control is being authored. A PictureBox control has been placed on the UserControl, and named picBase.

If the control you're enhancing has a minimum size, or a dimension that increases in large increments ? such as the height of a ListBox control, which changes by the height of a line of text ? you will have to add code to determine whether the Move method has produced the desired result.

You can rely on the control you're enhancing to handle painting, including (where appropriate) default button highlighting.

Tip   You may also have to add code to resize your UserControl object, to accommodate a constituent control that can't be sized arbitrarily ? such as a text box or list box. To avoid exhausting stack space with recursive calls to the Resize event, use static variables to determine when the UserControl_Resize event procedure is making recursive calls.

Resizing a Control Assembly
If you're authoring a control assembly, the Resize event will be more complex, because you have to adjust both size and relative location of multiple constituent controls.

Enforcing a Minimum Control Size
If you author a control with a large number of constituent controls, there may be a minimum size below which resizing the constituent controls is futile, because too little of each control is visible to be of any use, or because the enforced minimum sizes of some constituent controls has been reached.

There is no real harm in allowing the user to reduce the size of your control to absurdity. Most controls allow this, because preventing it is a lot of work, and because at some point you have to rely on your user's desire to produce an application that works and is usable.

In the event that resizing below some threshold causes your control to malfunction, you might make all of your constituent controls invisible, as an alternative to enforcing a minimum size.

The following code fragment provides a simple example of enforcing a minimum size.

Private Sub UserControl_Resize()
   Dim sngMinH As Single
   Dim sngMinW As Single

   ' Code to resize constituent controls. It is
   ' assumed that each of these will have some minimum
   ' size, which will go into the calculation of the
   ' UserControl's minimum size.

   sngMinW = <<Width calculation>>
   sngMinH = <<Height calculation>>

   If Width < sngMinW Then Width = sngMinW
   If Height < sngMinH Then Height = sngMinH
End Sub

Notice the <<pseudo-code placeholders>> for the calculation of your control's minimum width and height. These calculations will be in the ScaleMode units of your UserControl. They may be very complicated, involving the widths and heights of several of the constituent controls.

The Width and Height properties of the UserControl are then set, if necessary.

Important   The Width and Height properties of the UserControl include the thickness of the border, if any. If BorderStyle = 1 (Fixed Single), the area available for constituent controls will be reduced by two pixels (not Twips) in both width and height. If you have exposed the BorderStyle property for your user to set, include code to test the current value.

As an alternative, you could use the Size method:

   If Width > sngMinW Then sngMinW = Width
   If Height > sngMinH Then sngMinH = Height
   If (sngMinW <> Width) Or (sngMinH <> Height) Then
      ' (Optionally, set recursion flag.)
      Size sngMinW, sngMinH
      ' (Clear recursion flag, if set.)
   End If

This code is more slightly more complicated, but it simplifies things if you need to avoid recursion when resizing your control, as discussed below.

Important   The Width and Height properties of the UserControl are always expressed in Twips, regardless of the ScaleMode setting. If you have set ScaleMode to something other than Twips, use the ScaleX and ScaleY methods to convert your minimum size calculations to Twips.

Dealing with Recursion
No code for recursion is included in the example above, and recursion is virtually guaranteed. For example, if you attempt to resize the control so that both width and height are below the minimum values, the Resize event will reset the Width property, which will cause a second Resize to be raised immediately.

This second Resize event will test and reset the height, and then return ? so that by the time the first Resize event tests the height, it will already have been reset to the minimum. Clearly, this can lead to confusing debugging situations.

Even if you use the Size method, a second Resize event will occur, repeating all your calculations. This can be avoided by setting a flag when you deliberately resize the control. The Resize event should check this flag, and skip all processing when it is True.

A recursion flag is not necessary for simple minimum size situations, but is virtually required for more complicated scenarios.

For example, if you use the code above in a control whose Align property is True, so that it aligns to the form it's placed on, as described in "Making Your Control Align to the Edges of Forms," infinite recursion errors are guaranteed, until stack space is exhausted and an error occurs.

Important   Always use error handling in Resize event procedures. Errors here cannot be handled by the container, and your control component will therefore fail, causing the application using your control to fail, as well.