Link to home
Start Free TrialLog in
Avatar of ramses
ramses

asked on

Usercontrol Question + (continued)

Is there a bulletproof way to prevent resizing of a Usercontrol in VB5CCE?

(this question is started at 10 points, but I'll increase as soon as I have more points)

This question is started in

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

Please read that thread before adding comments here


Ramses
Avatar of ramses
ramses

ASKER

I got that nasty error 28 over and over again (out of stack space)



HELP!
That means that you removed or altered the code concerning the static variable. Can you post the current callback-procedure? We'll do something about it.

Have you read my last comment completely? Have you added the WM_MOVE?

rspahitz: you're right for "main" windows in windows 98+ and windows NT4+. But when you drag a control's handles on a form in visual basic, these rules do not apply. Visual basic only draws the content once the handles are released. To do so, VB doesn't sent any message (not even a HITTEST) to the control while you drag the handles. Once the handles are released though, the appropriate messages (WM_MOVE, WM_SIZE et al.) are sent.

Abel
Avatar of ramses

ASKER

Abel, I have read your comment.  Hold on, I'll get the code
Avatar of ramses

ASKER

Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Static RecursiveMsg As Boolean
    'Static OldMsg As Long
  'Dim xctl As UserControl1
 
  Select Case uMsg
  Case WM_WINDOWPOSCHANGED, WM_SIZE
      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
      'If uMsg <> OldMsg Then
        'Debug.Print "Message called: " + Trim(Str(uMsg))
        'OldMsg = uMsg
      'End If
      WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
  End Select
End Function

note the comments (')

these constants are declared at module level:

    Private Const WM_WINDOWPOSCHANGED = &H47
    Private Const GWL_WNDPROC = -4
    Private Const WM_GETMINMAXINFO = &H24
    Private Const WM_MOVE = &H3
    Private Const WM_SIZE = &H5
    Private Const WM_PAINT = &HF
    Private Const WM_ERASEBKGND = &H14
    Private Const WM_SETCURSOR = &H20
    Private Const WM_MOUSEACTIVATE = &H21
    Private Const WM_WINDOWPOSCHANGING = &H46
    Private Const WM_NCCALCSIZE = &H83
    Private Const WM_NCHITTEST = &H84
    Private Const WM_NCPAINT = &H85
Maybe this helps: make a seperate case for WM_SIZE and copy and paste the code. Make a new static variable for it. It's not so neat, but it'll show where the problem is. We'll beautify it later on.

I'm going to an appointment, I hope to be back in an hour or four to help you further.

Abel
Avatar of ramses

ASKER

OK

Just scream when you're back


Ramses
Avatar of Éric Moreau
declare a Static boolean variable in your resize event something like this:

Private Sub UserControl_Resize()
Static sblnResize As Boolean

    If sblnResize Then Exit Sub
   
    sblnResize = True
    'resize code here
    sblnResize = False
End Sub
Avatar of ramses

ASKER

No go Abel,

even with the 2nd case statement and static var, still Error 28 a few 1000 times after each other...

emouro, I'll try right away, hold on...



RAmses
Avatar of ramses

ASKER

No Emouro, doesn't work... sorry


Abel,

I've also tried adding error handling like this:

Function WindowProc...

...
ON ERROR RESUME NEXT
If err.number=28 then
  Unhook
  Msgbox "Stack Overflow Error, Hook disabled"
  Exit Function
else
  Err.Clear
End if
...

Suprizingly, I never get the Msgbox with Stack Overflow error...

But I see that, when adding the usercontrol to the form, the windowproc keeps getting called over and over again, until I close the form.  Then I reopen the form and find that the hook is somewhat working correctly, except for the fact that, when dragging some handles, the size is still not limited, but set to what the user has dragged.

Ramses
If you want a fixed size, do this:

Private Sub UserControl_Resize()
   UserControl.Height = 2000
   UserControl.Width = 3000
End Sub
Avatar of ramses

ASKER

I know vb fires a resize event when creating an object (even when it's a screensaver) but what causes this eternal loop when creating a usercontrol?
Avatar of ramses

ASKER

emoureau, please read the history of this question before commenting here


https://www.experts-exchange.com/jsp/qManageQuestion.jsp?ta=visualbasic&qid=20175004
You didn't have this eternal loop in the previous version of your code, did you? I mean, without the WM_SIZE in it. I wonder what went wrong. Can you go back to that code and get it to work again?

Also, very important here: after you alter the window procedure, make sure that you recreate the control: delete the control on the form and create a new one.
You know, I hate to suggest this, but the more I read the more I realize that when you're working with a flawed component which already has a fix, it's rarely worthwhile to try to find a workaround for the bug.  the amount of time you waste with this is usually ofset by the cost of buying the upgraded tool.

Therefore, my suggestion is to abandon the flawed tool and upgrade to VB 6.0 Professional (at about $600 or less.)  Let's see...at a low $20 per hour you break even after 30 hours of time wasted...and you get the benefit of other new and improved tools.
Eureka! Maybe this is it! Don't know if it will solve your problem, but maybe at least the stack-overflow. Remove the DefWindowProc line. You don't need it and it can cause this behaviour.

> Suprizingly, I never get the Msgbox with Stack Overflow error...
That is correct, VB doesn't allow you to do anything about it. It's a more severe error than can be handled by VB, and definitely not by Err.Clear.

> the windowproc keeps getting called over and over again
with the same event? If this still is the case after you remove the DefWindowProc line, please print the event and see if it's the same all the time.

> the size is still not limited, but set to what the user has dragged.
In what events of the UserControl do you call the hook function?
Avatar of ramses

ASKER

AbeL

It doesn't.  I have removed the DefWindowproc, but the eternal calling still happens.  Furthermore, the hook is set in both the InitProperties and Show eventprocedure of the Usercontrol.  I'll look right away what message is getting called.

rspahitZ

IF I was a programming and actually getting payed for it, I might be able to purchase VB 6.0 Professional.  In fact, I would buy it, as well as a higher programming language 4 windows, like C++ or delphi.  Since many things I want to program, I cannot do with my CCE edition.  Unfortunally I cannot pay $ 1.000 for VB6 pro, so I have to do with what I have.  

I do have managed to "steal" some controls from the Service Packs and licenses from VB4 so I have almost all the controls available.  For now, I'm stuck with what I can get my hands on for a low price.  Before, I could get hands on Crazy Bytes Cd's, stuffed with all kinds of things, including VB PRO's, but lately, I cannot find them anymore.  So if anyone from belgium's around here who knows where to get the CB's, please let me know. (text repeated in Dutch below)
Belgen, als je weet waar ik aan de Crazy-Bytes CD's met VB6 pro en/of MSVC kan geraken, laat aub iets weten

Maybe, someone can send me a copied cd of their version of MSVB6PRO?  Until then, I'm stuck with CCE and I do believe there must be a bullet-proof way to limit a usercontrols size.
Or how about, since it sounds like it's not proprietary code, that you make the code available to a trusted soul here to compile it for you?!
Avatar of ramses

ASKER

The message being called over and over again IS...





trtrtrtrtrtrtrtrtrtrtrtr



15




WM_PAINT





pretty strange for an empty usercontrol huh?



Ramses
Avatar of ramses

ASKER

Yes rspahitz that IS a possibility, but most controls I make are controls that are:

- invisable at runtime
- have no interface on the usercontrol
- have a picture on it to show what the control's about
  (like the CommonDialog Control)

It would be somehow stupide to ask someone to compile my ocx's each time while I can compile them myself.  Think man, THINK of a bullet-proof way to limit a usercontrol's size!  It MUST be possible.

Everyone

Are callbacks for the WindowMessages the only things that get fired upon resizing a control?


WAIT a minute!

WOuldn't it be possible to make a ocx or some other plug-in object that I can use in my ocx's, in C++ that checks the size of it's parent and prevents resizing?


Ramses
> Wouldn't it be possible to make a ocx or some other plug-in object that I
> can use in my ocx's, in C++ that checks the size of it's parent and prevents resizing?
I can do that in a breeze, but I'm afraid I'd have to charge you for that.

I have to go (again). I'll look through your comments later this evening. One thought though: is VB5CCE still downloadable? I can download it to try to mimick your situation.

Abel
Avatar of ramses

ASKER

Yes it is!  Hold on, I'll look it up.  Once sec...
Ok, I've read the comments so far that I didn't read yet. Here are some answers to open questions in this thread.

abel > Can you go back to that code and get it to work again?
Have you done this already? You didn't mention it anymore

ramses > but the eternal calling still happens.
Can you remove the commented code and post a cleansed copy here? Test it again after you have cleansed it and then post it, please. Make that a copy of *all* your code (like my original code-posting).

ramses > I could get hands on Crazy Bytes Cd's
Be careful with statements like that on EE. In europe, there's a manhunt going on after people selling CB cd's and the like and they succeed more often lately. Also, it is illegal to trade on EE, read the guidelines.

ramses > pretty strange for an empty usercontrol huh?
No, a WM_PAINT is *always* sent. But what I don't get is why that is causing a recursive loop. You don't handle the WM_PAINT, do you?

ramses > Are callbacks for the WindowMessages the only things that get fired upon resizing a control?
You might want to read something about windows, messages and callbacks. An unmissable book on the shelf when you work with these and VB is "Visual Basic Programming Guide To The Win32 API" by the famous Dan Appleman. You might be able to get an old copy of the book for little money. The first edition was written for VB4 but that shouldn't really be a matter.
But you want an answer, I guess, not some moralistic propaganda. The answer is "yes and no". Callbacks are not fired and you can put as many of them in a line as you like (but this is very dangerous). At this moment we are subclassing the control, but we could also hook it. But that would only complicate the matter and wouldn't give us any more possibilities. "Firing" is a VB-thing and something that large companies do. Windows "sends" messages to get things done, like WM_CREATE to create a window, WM_DESTROY to destroy a window or WM_MOVE to move a window. Try it out with SendMessage, you'll be surprised of the many posibilities.
In short: callbacks don't get fired, in VB-terms, messages get fired, in fact, an event is actually a message-trap.

What we are doing here is just some basic subclassing (yet another term, I'm sorry). In our case that means that we try to rewrite the resize message handler supplied by VB (UserControl_Resize). But we are stuck if no message at all is sent when the resizing in a particular direction takes place (the directions that we still cannot handle). But then again, how can a window resize if it doesn't get a message (it cannot, believe me) so we'll search further and we will find the right message to handle for this particular case. A message that surprisingly doesn't trap the Resize event, which it should, but hey, that's the bug we're solving, right?


There's something else I'd like to ask. Since your matter is addressed in Q171478 in the knowledge base, and since microsoft says to have fixed the problem in VS97SP2, I think it's a good idea to try again to install the service pack. Maybe something went wrong while you did it previously? You may want to look up that article on msdn.microsoft.com, so you can check to see if it's the same issue.

Abel
Avatar of ramses

ASKER

Abel,

You?ve asked quite a few questions.  I?ll address each of them, but out of order.

First of all, the Service Packs (SP) are meant for the real versions of the programming language (PL).  The ones where you have to pay for.  The SP?s don?t recognize Visual Basic Control Creation Edition (VBCCE) as a PL to be updated, so all I can do with the SP?s is update (manually) the controls that where shipped with VBCCE.

Because you insist, I?ll try again to install SP2, but my hopes are not so high.

Can I go back to that code and get it to work again?
I?ll try, but I?m not sure.  We?ve changed so many things.

I?ll remove all commented code and post a ?cleansed? copy here.

My remark about the CB?s wasn?t meant for real, just, how do you call it, ironic (I?m not sure about that word).  Anyway, I was just kidding.

ramses > pretty strange for an empty usercontrol huh?
I meant: ?Isn?t it strange that the WM_PAINT is being called OVER AND OVER AGAIN on an empty Usercontrol (UC)?  No, I don?t handle the WM_PAINT, just WM_WINDOWPOSCHANGED and WM_MOVE

?But we are stuck if no message at all is sent when the resizing in a particular direction takes place?  Messages are being send when dragging EACH handle to resize the UC, but the messages that are being send are not the same for each handle


This is the ?cleansed? code

MODULE 1

Option Explicit

Global gUserCtl As UserControl1
Global Const WM_WINDOWPOSCHANGED = &H47
Private Const GWL_WNDPROC = -4
Private Const WM_GETMINMAXINFO = &H24
Private Const WM_MOVE = &H3
Private Const WM_SIZE = &H5
Private Const WM_PAINT = &HF
Private Const WM_ERASEBKGND = &H14
Private Const WM_SETCURSOR = &H20
Private Const WM_MOUSEACTIVATE = &H21
Private Const WM_WINDOWPOSCHANGING = &H46
Private Const WM_NCCALCSIZE = &H83
Private Const WM_NCHITTEST = &H84
Private Const WM_NCPAINT = &H85

Global lpPrevWndProc As Long
Global gHW As Long

Private Declare Function DefWindowProc Lib "user32" Alias "DefWindowProcA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Sub Hook()
    lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, AddressOf WindowProc)
End Sub
Public Sub Unhook()
    Dim temp As Long

    temp = SetWindowLong(gHW, GWL_WNDPROC, lpPrevWndProc)
End Sub
Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Static RecursiveMsg As Boolean
  Static ScndRecursiveMsg As Boolean
    On Error Resume Next
    Debug.Print "Message called: " + Trim(Str(uMsg))
    If Err.Number = 28 Then
        Unhook
        MsgBox "Stack overflow error"
        Exit Function
    Else
        If Err <> 0 Then
            Unhook
            MsgBox "Other error: " + Err.Description + "(" + Trim(Str(Err.Number))
            Exit Function
        End If
    End If
   
  Select Case uMsg
  Case WM_WINDOWPOSCHANGED
      If Not RecursiveMsg Then
          RecursiveMsg = True
          gUserCtl.Height = 200
          gUserCtl.Width = 200
          RecursiveMsg = False
      End If
  Case WM_MOVE
    If Not ScndRecursiveMsg Then
          ScndRecursiveMsg = True
          gUserCtl.Height = 200
          gUserCtl.Width = 200
          ScndRecursiveMsg = False
      End If
  Case Else
      WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
  End Select
End Function


USERCONTROL1

Option Explicit


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
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

Avatar of ramses

ASKER

Sorry guys, it seems that not all characters are displayed properly... Replace ? with "

Ramses
Avatar of ramses

ASKER

I can't find the SP2 downloadpage anymore.  Anyone knows?


Ramses
> Sorry guys, it seems that not all characters
> are displayed properly... Replace ? with "
I guess you typed it in Word or WP and copied and pasted the text?

It's not SP2 anymore. I'm currently downloading SP3, but I'm on location and I don't know the SP's link by heart. I'll be back in the evening for that.

I think I might now the problem and the reason. Here's the catch:
When you first create the control you call Hook in InitProperties. The Show is fired and in that function you call Hook again. The function Hook sets the lpPrevWndProc variable, which holds a pointer to the previous window procedure. When we call it the second time, our WindowProc function *is* the current window procedure and that is set to lpPrevWndProc by the Hook function. Conclusion, lpPrevWndProc and AddressOf WindowProc point to the same function. Hence CallWindowProc calls our WindowProc and not the previous window function. Are you still with me? Good.
Now, to resolve this issue, just check the value of lpPrevWndProc before we set it in the Hook function as follows:

Public Sub Hook()
   If lpPrevWndProc = 0 Then
       lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, AddressOf WindowProc)
   End If
End Sub

That should do the trick. The only thing that puzzles me is *why* it worked in the first place. It should have bailed out with error 28 as well.

By the way, it good that you quoted "cleansed": I hoped for a smallest working version of the code as from our first postings, but this is not it. But never mind. It is still small enough to be clear :-)

Btw2: about WM_PAINT, that was exactly what puzzled me too (see my prev. comment) :-)

Btw3: Ironic is the right word :-)

I have downloaded CCE and installed it. It works as expected: it doesn't work correctly with the resizing the way you would expect. I'm going to try to install the SP3 myself and I'll keep you posted about my findings.

Rgds,
Abel
Avatar of ramses

ASKER

Thanks Abel
ASKER CERTIFIED SOLUTION
Avatar of abel
abel
Flag of Netherlands 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
btw: I also encountered the troublesome installation of the SP3 package. Just don't do it by hand, you'll probably just end up having more bugs then before.
Wow!  Good work abel.  I have no need to try it myself, but with all that work, it seems like it should be worth at least 200 points and an A-grade!  ramses--when do you think you'll have that much (or whatever you think it's worth)?

Avatar of ramses

ASKER

At the rate my points are increasing, I'd say in about 11 days.  I get 10 points a day.  Seems a bit strange but that's what happening.  When I first signed up, I was under the impression that when you solve a question and the answer is accepted, you get the points that the question was, and the expert points based on the grade.  How else are you supposed to get points?

Ramses

Abel, I'll try the code right away, as for the points, you'll notice an increase everyday.
Avatar of ramses

ASKER

ABEL


THIS IS GREAT!



IT WORKS WITHOUT CRASHING!


NO RESIZING ALLOWED FROM ANY HANDLE!


THANKS

BTW, CAN YOU ALSO ANSWER THE QUESTION ABOVE THIS ONE PLEASE





RAMSES
It should be that you get 5 pts a day, at least I do. When you answer questions, the amount of the points of the question is quadrupled, tripled, doubled or stays the same and is than added added to your "expert points" score. That's a separate score that has nothing to do with your "Question points" and it cannot be used to ask questions.

Great that it worked for you too. I hope you also understood some technical details in this thread, as when it does, it sure helps a lot when solving this kind of problems. Note one of the first comments in the previous thread where I pointed out that you cannot easily use this solution when you place more than one usercontrol on the same form, or even in the same project.

Btw, the book I mentioned is really the easiest to read and the best on the subject. It starts at the beginning and ends pretty/very advanced and it advances in an easy to follow pace.

You can just accept the comment with the answer as answer by clicking "Accept comment as answer" and awarding it as soon as you think you have raised the points for this question (and the prev. thread) to its right amount. I'll leave it up to you.

Groeten van je noorderburen! :-)

Abel
Avatar of ramses

ASKER

Ok Abel

Wouldn't it be better if I leave this question open until I got enough points to reward you?

Let's say we'll go to 200 points
This question's currently 90pts.
200-90=110
5pts/day
=110/5=22days
Kinda long huh?

Isn't there another way to get points, without cashflow?

Btw, the controls I make have functionallity in some way that 1 instance of them is enough.  But I guess you're right.  Users can put more instanses of a control on a form.  So I guess I have to anticipate it.  I know App.previnstance doesn't work in this case and I'll assume that if one creates another instance of the control, only the last added control's windowproc will be called. Hmm... maybe I'll try to solve this by making a list with control names, if I can access them.  But what about controlarrays?

Anyway, thanks for all the help, and I'll try to work something out.  Please let me know how you feel about the points-related comment in this text.


Ramses
Avatar of ramses

ASKER

I give up for now.

This is what I expierenced

When creating another instance of the control, the WindowProc now points to the 2nd control so when messages from the first control are send, they are intercepted by the 2nd control. I have no clue whatsoever to accomplish this.  Maybe I should just warn the user not to place more than one instance of the control per project?

When I think about it, I get it as follows:

memory
23239832 Usercontrol1
23239833  WindowProc

so, in the Hook procedure, you set SetWindowLong(blabla, AdresOff(WindowProc)  right?

now, I don't believe it's possible that two objects reside on the same adress, since a binary bit can only be 0 or 1, not 10 or 01, so in my thougts, it should be like this

memory
23239832 Usercontrol1
23239833  WindowProc
23239834   other code
23239835  ...
23239900 Usercontrol2
23239901  WindowProc
23239902  other code
23239903 ...

So, the address that the first UC sets for the event-trap is 23239833 and the 2nd UC is 23239901 both objects have another address in memory, so they shouldn't interfere with each other?  Right?  Then why isn't this right?  Abel, I know you suggest to read a book about it but thing is, I'm on a verry thight budget.  I can't spare an ? more (Jij kunt het Euro symbool toch zien he).

Anyway, I'll come to think that "bullet-proof" should actually mean that it handles multiple instances as well.  Just say it if you feel different aboout this.


Ramses
I'll come back on Monday, then I comment on those comments, ok?

Ket eurosymbool wordt bij mij een vraagteken, dat komt denk ik door de "translator" van EE.
(The eurosymbol becomes a questionmark when I look at it, I think that's due to the "translator" of EE)
If you have a very tight budget, you should ask for more: the level of programming has steeply raised from basic to highly advanced skills, particularly when you read through the code I'm going to present you in the next comment. To give you an idea, for a similar project, that took about as long to create this code for you (some nine hours) I'd charge about 1500 to 2000 guilders, depending on the customer. So, raise your rate!

I agree that bulletproof means that it should handle multiple instances as well, but I do not agree that I didn't give you that. All that it requires is some basic programming knowledge to extend this bulletproof version to a more bulletproof version. At least, that's what I thought.

I created this kind of controls and controls-subclassing very often, but never did it with VB5CCE and since VB6 emerged I never used VB5 anymore. In a normal scenario there are two approaches:

1. Singleton
You want the users to create only one instance of your control: make it a singleton. I understand from your previous comment that you don't know how to do it and I wonder if I can. I have managed it, but it's a bit hard and irrational in VB, let alone CCE. Funny thing is, use C++/ATL or C++/MFC and it's a breeze! (where have we heard that one before...)

2. Multiple instances
To make our subclassing solution bulletproof we need three things. First, create a dynamic array of the UC class, second, make the hWnd of each control public, third, lookup the right control in the window procedure. This all is part of a well-documented bag of tricks that can be achieved in a couple of ways. I choose the one without Dictionaries or Collections and my lookup-system is plain brute-force.

In any normal situation you are done with this. I created a small test-scenario to see if I could get it to work an partially it did, but I must say that I both overlooked a few things and encountered a few more strange behaviours of CCE. I've managed to overcome all problems except one: in the final version it is not possible to alter the UC while it is sited on a form. First close the form or remove the instance(s) before editing the code. Certain changes in the code will make VB crash and in the current approach you cannot help it, nor is there a workaround (unless you're going to use a completely different approach, but I'll leave that up to you).

Before you start copying and pasting the code, I want to invite you to read the next few comments as well as they will help you when you are going to make alterations to the code presented here. It's a bag of important things to know about COM/VB/CCE and the like. I'll try to keep it simple.

1. A module is loaded once
Indeed it is. Print the address of a function in a module from several instances of you object and they will all point to the same address. You make it appear in your comment as if each object will have its own window procedure, but it has not. They all share the same window procedure and there's (almost) nothing you can do about it. Even in C++ this is almost impossible to overcome, at least not without assembler, so just don't think about it, it's not worth it. Just work with one module that is shared amongst all you UC's and all it's objects that appear in the same process.

2. All data in the module is loaded once
As above, this holds true also. A public array in a module will be loaded once. You can use this information to see if there's already an instance of your object and give an error if there is.

3. Sub Main is called once
When you configure your UC to start from sub main, that is, as follows from 1 and 2, also called once and *before* any UC is instantiated.

4. Each UC will take its datasize in memory
When you're going to read memory addresses again, remember then that each UC is loaded at a memory address and takes up as much space as is needed for all its datamembers. Its functions, even if they are many, are stored elsewhere and will not show up in the size of the UC in memory. Nevertheless, Len(UC) will always give 4: the amount of bytes needed to store a pointer, ie, the pointer to the first memory address.

5. Siting and unsiting
These are very important events, but you cannot access them without subclassing in VB. A control sites when it attaches itself to the parent and it unsites when it detaches itself. Quite a while earlier/later the initialize and terminate events are called. I use the unsite-event in the code.

6. When you run a project, the UC's are dismissed
You might expect a neat shutdown of a UC, but then part of it could stay in memory depending on how you code your component. To overcome this, VB fires the Terminate event, even if more then one reference to the UC still exist. This means that the unsite event (message) is not fired (sent) and that VB unloads the whole UC just after the Terminate event.  This is unfortunate and to abrupt, but in the code this has been solved easily.
The problem was that the data-part was zeroed by VB but the executable part still existed in memory. Hence the WindowProc got called but ran into trouble, because of the zeroed data. To understand what I mean, remove the statement in the Terminate event and run the project.

7. Show event is not called when siting the first time
I've always found this ridiculous. If I'm not mistaken, this is the same in all VB versions. There's no single point of entry that you can code for the moment that the object visualizes. You at the very least have to use InitProperties and Show, of InitProperties and ReadProperties, which is a bit unintuitive, don't you think?

8. There's a lot more but I can't think of it anymore
I encountered a lot more things and a lot more advanced VB techniques (or API techniques) have been used. Probably a lot of them will go unnoticed, but who cares. It's working, isn't it?



When you read through the code, and I hope you will, I hope you understand the lot of it. Most of the stuff is pretty basic and the overall structure is kept to the simplest I could think of for this scenario. It's amazing what people do just to retain a certain size!

Regards,
Abel
Here's the code. It should be self-explanatory after you read the comment above and when you read the comments in the code.

'*********************************
'** Module part
'*********************************

Option Explicit

Public gUserCtl() As UserControl1
Public iCtlCount As Integer

Public Const GWL_WNDPROC = -4
Public Const WM_DESTROY = &H2
Public Const WM_WINDOWPOSCHANGING = &H46
Public Const SWP_NOSIZE = &H1

Public Type WINDOWPOS
        hWnd As Long
        hWndInsertAfter As Long
        x As Long
        y As Long
        cx As Long
        cy As Long
        flags As Long
End Type

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Sub CopyMemoryToWindowPos Lib "KERNEL32" Alias "RtlMoveMemory" (hpvDest As WINDOWPOS, ByVal hpvSource As Long, ByVal cbCopy As Long)
Private Declare Sub CopyMemoryFromWindowPos Lib "KERNEL32" Alias "RtlMoveMemory" (ByVal hpvDest As Long, hpvSource As WINDOWPOS, ByVal cbCopy As Long)
Public Declare Function DefWindowProc Lib "user32" Alias "DefWindowProcA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long


Public Sub AddUserCtl(newCtl As UserControl1)
    'inserts new ctl in array
    Dim iCtl As Integer
    Const ArrayStep As Integer = 2
   
    If iCtlCount Mod ArrayStep = 0 Then
        'increase array
        ReDim Preserve gUserCtl(iCtlCount + ArrayStep)
    End If
    For iCtl = 0 To iCtlCount - 1
        If gUserCtl(iCtl) Is newCtl Then
            Exit For
        End If
    Next iCtl
    If iCtl >= iCtlCount Then        'not found, add
        Set gUserCtl(iCtlCount) = newCtl
    Else                            'found, do no add
        'nothing to do
    End If
    iCtlCount = iCtlCount + 1
End Sub

Public Sub RemoveUserCtlByHwnd(hw As Long)
    'Removes ctl from array and reorders array
    Dim bFound As Boolean
    Dim i As Integer
    If iCtlCount = 0 Then
        Exit Sub
    End If
    For i = 0 To iCtlCount - 1
        If gUserCtl(i) Is Nothing Then
            Exit For
        End If
        If Not bFound Then
            bFound = gUserCtl(i).hWnd = hw
        End If
        If bFound Then
            If gUserCtl(i).hWnd = hw Then
                Set gUserCtl(i) = Nothing
            End If
            Set gUserCtl(i) = gUserCtl(i + 1)
        End If
    Next i
    iCtlCount = iCtlCount - 1
End Sub

Public Function FindUserCtlByHwnd(hw As Long) As UserControl1
    'look up correct userctl
    Dim iCtl As Integer
   
    For iCtl = 0 To iCtlCount - 1
        If gUserCtl(iCtl) Is Nothing Then
            iCtl = iCtlCount
            Exit For
        End If
        If gUserCtl(iCtl).hWnd = hw Then
            Exit For
        End If
    Next iCtl
    Set FindUserCtlByHwnd = gUserCtl(iCtl)  'Returns Nothing if not found
End Function

Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Dim usrCtl As UserControl1
   
    Select Case uMsg
    Case WM_WINDOWPOSCHANGING
        Dim wp As WINDOWPOS
        CopyMemoryToWindowPos wp, lParam, Len(wp)
        If wp.cx <> 200 And wp.cy <> 200 Then
            wp.flags = wp.flags Or SWP_NOSIZE
        End If
        CopyMemoryFromWindowPos lParam, wp, Len(wp)
       
        Set usrCtl = FindUserCtlByHwnd(hw)
        If usrCtl Is Nothing Then
            WindowProc = DefWindowProc(hw, uMsg, wParam, lParam)
        Else
            WindowProc = CallWindowProc(usrCtl.OldWndProc, hw, uMsg, wParam, lParam)
        End If
    Case &H100E     'Some of VB's own messages (undocumented!)
        'The Terminate event does not fire because we hold a reference to it in the array
        'that's why we cannot unhook in the Terminate event (it will never be called!)
        'This message is the WM_VBSITE message (wp=37 means site, wp=38 means unsite) and
        'is fired whenever a control is (un)sited.
       
        If wParam = 38 Then     'UNSITE message, the visual representation of the object is
                                'about to be be destroyed
            Set usrCtl = FindUserCtlByHwnd(hw)
            If usrCtl Is Nothing Then
                WindowProc = DefWindowProc(hw, uMsg, wParam, lParam)
            Else
                SetWindowLong hw, GWL_WNDPROC, usrCtl.OldWndProc
                RemoveUserCtlByHwnd hw
            End If
        End If
    Case Else
        Set usrCtl = FindUserCtlByHwnd(hw)
        If usrCtl Is Nothing Then
            WindowProc = DefWindowProc(hw, uMsg, wParam, lParam)
        Else
            WindowProc = CallWindowProc(usrCtl.OldWndProc, hw, uMsg, wParam, lParam)
        End If
    End Select

End Function




'*********************************
'** UserControl part
'*********************************

Option Explicit

Private lPrevWndProc As Long            'Previous window procedure for this object

Friend Property Get OldWndProc() As Long
    OldWndProc = lPrevWndProc
End Property

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

Private Sub UserControl_Click()
    Debug.Print "Clicked!!!"
End Sub

Private Sub UserControl_InitProperties()
    UserControl_Resize      'Resize *before* subclassing, after that it's not allowed anymore!
    AddUserCtl Me
    lPrevWndProc = SetWindowLong(UserControl.hWnd, GWL_WNDPROC, AddressOf WindowProc)
End Sub

Private Sub UserControl_Resize()
    'Seems redundant, but is needed to show VB that the size has changed
    'To understand what happens otherwise, just remove these two statements and it will clarify for you
    'once you draw the object and try to resize it
    Height = 400
    Width = 400
End Sub

Private Sub UserControl_Show()
    If lPrevWndProc = 0 Then
        UserControl_Resize      'Resize *before* subclassing, after that it's not allowed anymore!
        AddUserCtl Me
        lPrevWndProc = SetWindowLong(UserControl.hWnd, GWL_WNDPROC, AddressOf WindowProc)
    End If
End Sub

Private Sub UserControl_Terminate()
    If lPrevWndProc <> 0 Then
        'Sometimes Terminate is called without first sending the unsite message (see WindowProc)
        'This happens ie. when you hit the run button and the object is still loaded in memory.
        SetWindowLong UserControl.hWnd, GWL_WNDPROC, lPrevWndProc
    End If
End Sub


Note that the code has become increasingly complex. You'll have to be very caustious altering it. Due to its complex nature it'll be also much harder to help you when things fail in the future and whenever they do and you mention it, you'll have to very specific about the circumstances and the messages the appeared.

Yet another note, I usually don't supply people on EE with copy and paste solutions, but this time has been an exception. I was intrigued by the simple question and it's hard solution and that has been your luck here.
About the points, don't worry too much about them, but I would appreciate it if you'd let this question increase to 200 pts at least, if only for people buying this PAQ: they'll have to pay for this, and a lot, don't you think?

About the 10 pts daily increase of your points, I think that's because you're a Devx member. You have applied for Knowledge Pro, either as an expert or you payd for it. The points system works a bit differently then.


Regards,
Abel
Avatar of ramses

ASKER

Wow, Abel


I feel kinda guilty for "sqeezing" the code from you now I know in full extend how complex it is.

Anyway, this question's worth more than 200Pts in my eyes and I will search for a solution to reward at least 500Pts.  Please allow me some time to do so.  In the mean time, whenever you want this question to be PAQ'ed, just let me know, the remaining points will then be posted, when I have them, in a new Question with title "Points for Abel", in this TA  Off course, there's an A-grade for this question, actually, they should offer a possibility to give A+++ grades or something like that.

As for the rates, you're right, I should ask more, but often I'm forced to accept lower rates because the competition is so very hard.  Especially when you have to do with so little income, one is happy to accept a job for less, then let it go because I ask to much.  The live of an unemployed is far from easy and not seldon do I fear the end of the month because there's to little money to last...


Anyway, I thank you for your effort, and you will be rewarded.  If, at anytime, I can help you with something, just let me know.  I have no fear to post my email address here because I have some very good spam filters and I got quite good at unsubscribing spam lists.

Anyway, my address is secretaris@vep.be



With kind regards
Vriendelijke groeten van je zuiderburen!



Ramses
Avatar of ramses

ASKER

Btw Abel, you're right about the 5Pts (not 10)
Avatar of ramses

ASKER

But, while this question remains open, everyone can access it for free.  At this rate it's going to take an additional (100/5=20) 20 days for this to be PAQ'ed


Ramses
Well, I don't mind it being open. I only meant that, once it is rewarded, let it be for some points. But at the other hand I don't want you to be refrained from asking questions because you'd have to wait until you have enough points. I'm not that active on this site anymore and the points don't matter that much to me that I want you spend all of yours on a single question only because MS was too lax to provide descent software. Let's say you wait to 150 and then you reward the points, that should be enough (maybe not for the answer to the question, but at least enough for me).

Abel
Btw:
Here's a simple way to enforce singleton-like behaviour, in answer to "the controls I make have functionallity in some way that 1 instance of them is enough"

In a class (be it a UC or something else) create a static variable of it's own type. Then, in the Initialize-event you set that variable to "Me" (the object me, itself I mean) when it's nothing and when it's already something you raise an error warning that only one instance is allowed.

This trick works with any object that you create and as well in design time as in run time. It is not real "singleton" behavior, but it's close and at least you prevent people from having more then one object in there project.
Avatar of ramses

ASKER

Thank you Abel!
Avatar of ramses

ASKER

As for your previous comment, I agree, after ten more days this question will be paq'ed with an A grade.  But even when paq'ed, people can purchase it for a mere price of 15pts (10%)

Wouldn't it be better in this case that you had just explained (like just now) how to prevent multiple instances?  Don't get me wrong on this, it has helped me a lot, and when manually editing the form files, one can add more instances, and I don't know if that singleton simulation will work then, so it's better to have that code, kinda not look foolish to the end-user and getting comments that his/her grandson could do better and all.

Anyway, you're right about the lazyness of microsft people and I do understand that this question has cost many people (mostly you) a lot of energy and you want people to pay a higher price for purchasing the result.  I have one more question dough.  When people purchase a paq, do they get the whole thread or just the answer?  I mean, on many occassions (including this one), some vital information is provided in the thread, and the answer is just completing the whole.  I mean, just by looking at the answer, one is not completely helped if he/she doesn't know the rest of the information that was provided in the comments.  I know, I know, I often tend to ramble on and on, so I'll stop right now.


Once again, Abel, thank you very much



Ramses
Anyone buying a PAQ gets the entire discussion (although not linked questions.

To answer your other questions: how do you get more points for "free"?  The answer is to become an expert with 15,000 expert points, then wait a month and if you maintain 3000 points per month you get 500 to use as needed.

Personally, I find the benefit of this when I ask questions in other topic areas since if you're getting 3000 points each month, you must know a thing or two about the current topic.
Avatar of ramses

ASKER

thanks rspahitz
rspahitz, nice to know that you're still here :-)
I have a question for you. Do you recall what happened with Otta? I think I saw you participating the discussion about him. What puzzled me was what happened (the actual q. got deleted) and what ever came over him, he was a member since '97 already.
I know it's not good to start a discussion about him here, cluttering this thread, but maybe you can lift the veil a little?

Anyway, back to the subject. I don't think it's good to make an object into a singleton unless you really need to. If there is any reason why a user might want (or: need!) to create more than one object, give them the possibility to do so. Singletons are mostly useful as an out-of-process executable COM object, for example, a central repository of data. But I'll leave the judgement of do or don't to you, of course.

There's another good book, again by Dan Appleman, that is the most authoritative on this for users of "Visual Basic: Com/ActiveX Components with Visual Basic 6". I know you're on a tight budget and that you have little to spend, but whenever you have the possibility or when you really find the need to buy a book but you can't think of any, then think of this one (or the other I mentioned earlier).

Abel

PS: Have you utilized the code already? Just curious.
Abel, I remember Otta's name, but not the context.  If you want to pursue this, drop a Q in the Expert Input area ("What ever happened to Otta?"), then put the link here if I don't get to you in a reasonable time.
Well, it's not that important. He just had its account deleted by EE because of his "alleged infractions". Let's just leave it.
Avatar of ramses

ASKER

Yes Abel

I have tried the code provided by you and it does WORK, even with multiple instances.

I think I will let go of the singleton thing because even though one instance will do the trick, I've seen people use more than one instance of CommonDialog control too and I wouldn't feel right to limit that kind of functionality.  But anyway, the code works GREAT!  As a token of my appreciation, I intend to place your text in the aboutbox like "No-resize code by Abel" or something like that, just let me know how you feel about it...

I have put the two books you mentioned on my purchase list and in time (if I can find them in this hellhole) I will buy them, once again, Thank You!


What are "alleged infractions"?
> What are "alleged infractions"?
From the dictionary: "zogenaamde of beweerde schendingen/inbreuken". He didn't say of what exactly, that was also why I was curious.

> intend to place your text in the aboutbox
I'm kinda honoured :-)  but I don't think anybody will understand that non-resizing is difficult and then they won't understand why my name is there for such a simple task. I don't mind you using my name, but don't feel obliged either.

Btw, I forgot something. Did you notice that I use another way of dealing with the resizing then from the original postings?

Most people use more than one commondialog control (or filelist for that matter) when they have more than one form that needs them. Of course it is not necessary, but some people probably find that easier. What is the functionality of your control that it is dubious to be a singleton or not?

Abel
Avatar of ramses

ASKER

They're plugins I make, not really plugins in the real meaning of the word but they are ment to provide additional functionality to another control, also made by me.  To give you an example, I cannot reveil what it actuallyd does (bound by confidentiality) but I can explain it with something else

Note that you cannot compare the examples with the acutall work.

consider a calculator where you have the cpu, then you could somehow say the controls I make are the buttons for the calculator, you have one 1, one 2, one 3,...  No sense in putting to 1 buttons on it.  Know what I mean?


I know it sounds silly and people will definatly don't understand, comprehend that it took so much trouble to prevent resizing... maybe I should reprhase it to Sublassing provided by Abel or something like that.  It's just my habbit of thanking the people who helped, with they're names in the credits.  If it's too silly to describe, I could also go like this:  "The author wishes to thank the following people for contributing to this project:"

Abel
...


Ramses
I think I understand it at least a bit, but it is vague, to say the least.

I like the "wishes to thank" version :)

Rgds,
Abel
Avatar of ramses

ASKER

Consider it done Abel.  I know it's very vague, but I'm cannot reveile info regarding this because it was my clients wish, I have to respect that.  Do I refer to you as Abel from DevX or just Abel?


Ramses
I'm an Experts-Exchange member (https://www.experts-exchange.com), not a DevX member (although my company is). DevX members use usually http://knowledgexchange.devx.com, which has the same functionality as Experts-Exchange, but another look and feel (only slightly different though). You are a DevX member, aren't you?
You may use just Abel, or Abel Braaksma (full name) or Abel from Experts-Exchange. I consider all to be fine referals.

Abel
Avatar of ramses

ASKER

Thanks, I'll go with Abel Braaksma and a link to either devx.experts-exchange.com or www.expers-exchange.com or knowledgexchange.devx.com.  Should you want a link to a particular page (your site or whatever) please let me know.  If you don't want to post this information here, just send it to straight-a-software@vep.be .  Spammers be warned that I have very good filters that spam the spam back to the spammer... you won't like it!


Yes, I'm a DevX member and I haven't been bother with that Pop-Up add from Pop-Off :-) because I always comein in the All Topics page (from Favorites)


Ramses
Avatar of ramses

ASKER

Almost there... :-)
Yep :)
Hope it didn't refrain you from asking questions, because I wouldn't want that to happen.
Avatar of ramses

ASKER

I'm kinda bussy with other problems right now; I have a assignment to make a piece about the September 11th attacks (to look up as much links as possible).  And since this is a world-wide covvered event, I think I'll be bussy for a while...


Ramses
Avatar of ramses

ASKER

Thanks guys... until the next question then :-)


Anyone who purchases this PAQ, please take some time to read the WHOLE thread, and the previous one.  That one's a 0-Pt question, so you don't have to purchase it.

Ramses says RoOAAar!
Abel, please see https://www.experts-exchange.com/questions/21154040/Points-for-Abel.html



With kind regards



x_terminat_or_3 (Ramses)
Hi Ramses,

Hmm, that question appears to be deleted. What was it about that you specifically request my attention to it? Problems with the resizing solution? Or the hooking involved?

I'll here from you,

Regards,
Abel
Hi Abel


I just wanted to give you more pts for your help but I'm not alowed by EE....




Anyway I just wanted to know that I learned much from your help.



With kind regards


x_terminat_or_3 (Ramses)
That's very kind of you. It's nice to hear every now and then that people appreciate what you do.

It has been awhile, but I can still remember the issue and the research. It has been a pleasure working with you.

Kind regards,
Abel
It has been a pleasure working with you to Abel.

Ramses