'' Author: David Burkley '' Email: burkleyd@yahoo.com '' Date: May 16th, 2009 ' ' So how do you create an array of custom components defined within a Type structure? ' ' Well... for starters... RapidQ doesn't allow you to create it in the way that you ' would think it should be (could be) done. Such as... ' ' Type MyCustomComponent Extends QObject ' *define your custom component* ' End Type ' ' Dim MyObject(10) As MyCustomComponent ' ' Instead... you need to do it this way... ' ' Type MyCustomComponent Extends QObject ' *define your custom component* ' End Type ' ' Dim MyObject As MyCustomComponent<10> ' ' Within your Type structure... you need to create arrays of the Properties and then ' include an index number (into the array) as a parameter for your Methods. ' (I haven't tried creating Events that reference an index.) ' (So I'm not sure if it can be done.) ' ' Your property arrays would look something like... ' Left(size), Top(size), Width(size), Height(size) ' ' And then when you need to reference one of the components arrayed properties ' you would use this... ' MyObject.Left(3), or MyObject.Top(1), or MyObject.Width(4), or MyObject.Top(2) ' ' Your methods would look something like... ' Sub MoveLeft(index, value) *index = the index into the array, value = how much to move left* ' ' And then you would call your method this way... ' MyObject.MoveLeft(n, 10) *n = the index, 10 = move that component left 10 pixels* ' ' The concept hasn't changed. Only where you're putting the array "index" has changed. ' Instead of the array index being part of the "object"... ' it's part of the property, or a parameter passed to the method. ' ' Below is a example of how to do it. ' NONE of the APIs or Constants are required in YOUR source code. ' 99% of this is simply for demonstration purposes. ' The only thing that's needed is... the "concept" of how it should be coded. ' It might seem like more work... but if you REALLY need arrays of custom components... ' this is the only way I know of how to do it. ' ' I chose to create 4 panels using the API's "Static" component as an example. ' Sure it's easier to create Panels in RapidQ. ' But that's not what this example's about. ' It's about... "how" to create an array of custom components. ' ' Special Note: There IS one catch to all this!!! ' Once you set the number of components that will be created... ' you can NOT change it later on. My advice... create a large ' enough array of components to suit your needs BUT don't go ' creating it so large that most will go un-used. You'll just ' be wasting memory. ' ' Oh... one more thing. I can't see turning this into a $Include file. ' Everybody's going to create different kinds of custom components. ' And each will have it's own special needs for Properties and Methods. ' All this is good for is a guideline as to how to do it. $Apptype GUI $Option EXPLICIT $Include "RapidQ.inc" 'Using this to allow the form to be minimized to the taskbar. Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _ (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long 'Start of stuff used to create and manipulate the API created Panels. Declare Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" _ (ByVal dwExStyle As Long, ByVal lpClassName As String, _ ByVal lpWindowName As String, ByVal dwStyle As Long, _ ByVal x As Long, ByVal y As Long, _ ByVal nWidth As Long, ByVal nHeight As Long, _ ByVal hWndParent As Long, ByVal hMenu As Long, _ ByVal hInstance As Long, lpParam As Long) As Long Declare Function DestroyWindow Lib "user32" Alias "DestroyWindow" _ (ByVal hWnd as Long) As Long Declare Function SetParentAPI Lib "user32" Alias "SetParent" _ (ByVal hChild As Long, ByVal hNewParent As Long) As Long Declare Function GetParentAPI Lib "user32" Alias "GetParent" _ (ByVal hWnd As Long) As Long Declare Function GetClientRect Lib "user32" Alias "GetClientRect" _ (ByVal hWnd As Long, lpRect As QRect) As Long Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" _ (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, _ ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" _ (ByVal hwnd As Long, ByVal lpString As String) As Long Const GWL_HWNDPARENT = (-8) Const HWND_DESKTOP = &H0 Const WS_EX_WINDOWEDGE = &H100 Const WS_EX_CLIENTEDGE = &H200 Const WS_VISIBLE = &H10000000 Const WS_CHILD = &H40000000 Const SS_CENTER = &H1 Const SS_SUNKEN = &H1000 Const SWP_FRAMECHANGED = &H20 Dim ClientRect As QRect 'End of stuff used to create and manipulate the API created panels. 'Here's where the fun starts. ' Note the "size" parameter. ' It's sets the number of components that will be created. Type MyPanels Extends QObject PRIVATE: 'this prevents access to these variables (from outside of this structure). sString As String 'local variable used to temporarily hold the Caption while resizing. Parent(size) As Long 'None of these necessarily need to be in the "Private" section. Left(size) As Long 'I only chose to put them here to show you that it can be done. Top(size) As Long '(if need be) Width(size) As Long Height(size) As Long Caption(size) As String PUBLIC: 'this allows access to these variables (from outside of this structure). Align As Byte Tag(size) As Byte Handle(size) As Long 'And because everything below here is "still" in the Public section... ' access them is available from outside of this structure. 'You'll notice setting a property is done within a subroutine ' and that you need to pass 2 parameters (the index and the value for that property) 'Getting a property value is "safely" done within a function ' with just 1 parameter passed to it (the index). ' Why do I say "safely" for getting a property value? ' Because that value could be changed by any number of outside influences. ' You could simply skip using a function to get the value. ' But using a function ensures you're getting the most current value. ' Typically the best way to get the most current value for a property ' is to use some API function. But that's not always possible. In those ' cases... you'll have to rely on whatever is stored in a variable and ' "hope" it's current. With This 'Set a property Sub SetCaption(index As Byte, value As String) If .Caption(index) <> value Then .Caption(index) = value 'Only call the API function if the object exists. If .Handle(index) <> 0 Then SetWindowText(.Handle(index), value) End Sub 'Get a property Function GetCaption(index As Byte) As Long Result = .Caption(index) End Function 'Method for changing a property (which might affect the look of your component). 'This Method gets called numerous times (even without you knowing it). 'If you're wondering... what's the API GetClientRect is for? 'It'll be explained further on down... in this source code. Sub Resize(index As Byte) 'Only call the API function(s) if the object exists and/or it's parent exists. If .Parent(index) <> And .Handle(index) <> 0 Then If .Align = alClient Then GetClientRect(.Parent(index), ClientRect) .Width(index) = (ClientRect.Right\2) .Height(index) = (ClientRect.Bottom\2) .Left(index) = IIF(((index Mod 2) = 0), 0, (ClientRect.Right\2)) .Top(index) = IIF((index < 2), 0, (ClientRect.Bottom\2)) End If .sString = .GetCaption(index) 'resizing the panel causes distortion in the caption. .SetCaption(index, "") SetWindowPos(.Handle(index), 0, .Left(index), .Top(index), .Width(index), .Height(index), SWP_FRAMECHANGED) .SetCaption(index, .sString) 'restore the caption after the resizing has occurs. End If End Sub 'Set a property Sub SetParent(index As Byte, value As Long) As Long If .Parent(index) <> value Then .Parent(index) = value If .Handle(index) <> 0 Then SetParentAPI(.Handle(index), .Parent(index)) End Sub 'Get a property (here's a classic example of the possiblity of a ' property that might have gotten changed by an outside influences) ' Using an API function will get you an exact current result. Function GetParent(index As Byte) As Long If .Handle(index) <> 0 Then Result = GetParentAPI(.Handle(index)) End If End Function 'Set a property Sub SetLeft(index As Byte, value As Long) As Long If .Left(index) <> value Then .Left(index) = value If .Handle(index) <> 0 Then .Resize(index) End Sub 'Get a property Function GetLeft(index As Byte) As Long Result = .Left(index) End Function 'Set a property Sub SetTop(index As Byte, value As Long) As Long If .Top(index) <> value Then .Top(index) = value If .Handle(index) <> 0 Then .Resize(index) End Sub 'Get a property Function GetTop(index As Byte) As Long Result = .Top(index) End Function 'Set a property Sub SetWidth(index As Byte, value As Long) As Long If .Width(index) <> value Then .Width(index) = value If .Handle(index) <> 0 Then .Resize(index) End Sub 'Get a property Function GetWidth(index As Byte) As Long Result = .Width(index) End Function 'Set a property Sub SetHeight(index As Byte, value As Long) As Long If .Height(index) <> value Then .Height(index) = value If .Handle(index) <> 0 Then .Resize(index) End Sub 'Get a property Function GetHeight(index As Byte) As Long Result = .Height(index) End Function 'Get a property Function GetHandle(index As Byte) As Long Result = .Handle(index) End Function 'For this demo... I need to create 4 API "STATIC"s and this is how I did/do it. Sub CreateNew(index As Byte) 'Only call the API function (to create a Panel) if... ' "everything" exists and has an acceptable value. If .Parent(index) <> 0 And .Left(index) > -1 And .Top(index) > -1 And .Width(index) > 0 And .Height(index) > 0 Then .Handle(index) = CreateWindowEx(WS_EX_WINDOWEDGE Or WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD Or WS_VISIBLE Or SS_CENTER Or SS_SUNKEN, .Left(index), .Top(index), .Width(index), .Height(index), .Parent(index), 0, Application.hInstance, 0) End If End Sub 'API created components need to be destroyed manually when the application terminates. Sub Free(index As Byte) If .Handle(index) <> 0 Then DestroyWindow(.Handle(index)) End Sub End With 'You're probably wondering... why no Constructor? 'Well... it's not 'absolutely" necessary unless you need to pre-define some values ' in order to get things to work right. End Type Declare Sub MainForm_OnShow Declare Sub MainForm_OnClose Declare Sub MainForm_OnResize Declare Sub mTest_OnClick 'A variable that sets the maximum number of custom components ' to create (remember not to make it any larger than what you need). 'It's also used as a reference as to how many components were "supposedly" made. Dim MaxCnt As Byte MaxCnt = 3 'Just a generic variable used to create and manipulate the components. Dim i As Byte Dim IsFormShowing As Byte Create MainForm As QForm Caption = " Type Array Demo" Width = 640 Height = 480 Left = (Screen.Width\2)-(MainForm.Width\2) Top = (Screen.Height\2)-(MainForm.Height\2) OnShow = MainForm_OnShow OnClose = MainForm_OnClose OnResize = MainForm_OnResize Create MainMenu As QMainMenu Create mTest As QMenuItem Caption = "Test" OnClick = mTest_OnClick End Create End Create 'Here is the reason for the API GetClientRect function. ' I'll be setting this RapidQ QPanel as the "Parent" ' for the API created Panels. Why? Because the API created ' Panels do not communicate with the QForm. The QForm ' doesn't (and won't) even know they exist. So it can't ' send messages to them when they need to be resized. The ' QForm does know about the QPanel and communicates with ' it. So rather than writing TONS of code to get the ' API to communciate with the QForm... I'm simply sizing ' the API Panels to match whatever the QPanel's current ' size is. It might not be the proper way to do it but... ' it works. :) Create SizingPanel As QPanel Align = alClient End Create Create Statusbar As QStatusbar Align = alBottom AddPanels(" ") SizeGrip = False End Create End Create 'And here's where you create an array of instances for your custom components. 'It doesn't look like the normal way you'd create the array ' but this is how it's done in RapidQ. Dim Panel As MyPanels ' MaxCnt was pre-defined up above. 'Now that an array of instances has been created... ' do whatever needs to be done to created your custom components. For i = 0 To MaxCnt Panel.SetParent(i, SizingPanel.Handle) Panel.SetWidth(i, (SizingPanel.ClientWidth\2)) Panel.SetHeight(i, (SizingPanel.ClientHeight\2)) Panel.SetLeft(i, IIF(((i Mod 2) = 0), 0, (SizingPanel.ClientWidth\2))) Panel.SetTop(i, IIF((i < 2), 0, (SizingPanel.ClientHeight\2))) Panel.Align = alClient 'Make sure you've got ALL the necessary properties set "before" you try creating it. Panel.CreateNew(i) Panel.Tag(i) = i Next i SetWindowLong(MainForm.Handle, GWL_HWNDPARENT, HWND_DESKTOP) SetWindowLong(Application.Handle, GWL_HWNDPARENT, MainForm.Handle) MainForm.ShowModal Sub MainForm_OnShow IsFormShowing = True End Sub Sub MainForm_OnClose 'Since the Panels were created in a non-RapidQ way... ' they need to be destroyed manually. For i = 0 To MaxCnt Panel.Free(i) Next i Application.Terminate End Sub Sub MainForm_OnResize 'Before the QForm is actually "seen" on your computer screen... ' this subroutine gets executed twice. Once to size the horizontal ' aspects. And then again to size the vertical aspects of the form. ' In between those two executions... the form's OnShow gets executed. ' So you only want to run the resize subroutine for the API created ' Panels during the 2nd execution. Otherwise you'll get an exception error. ' After the form is displayed on screen... this subroutine will only get ' executed if the form is resized (including minimized and maximized). If IsFormShowing = True Then For i = 0 To MaxCnt Panel.Resize(i) Next i End If End Sub Sub mTest_OnClick 'Strictly for testing purposes. 'When this subroutine gets executed... ' each API Panel that was created will display ' some information about itself as proof that ' an array of custom components was indeed created. DefStr sString = "" For i = 0 To MaxCnt sString = Chr$(10) & Chr$(10) & Chr$(10) & _ "Panel.Parent(" & Str$(i) & ") = " & Str$(Panel.GetParent(i)) & Chr$(10) & _ "Panel.Handle(" & Str$(i) & ") = " & Str$(Panel.GetHandle(i)) & Chr$(10) & _ "Panel.Left(" & Str$(i) & ") = " & Str$(Panel.GetLeft(i)) & Chr$(10) & _ "Panel.Top(" & Str$(i) & ") = " & Str$(Panel.GetTop(i)) & Chr$(10) & _ "Panel.Width(" & Str$(i) & ") = " & Str$(Panel.GetWidth(i)) & Chr$(10) & _ "Panel.Height(" & Str$(i) & ") = " & Str$(Panel.GetHeight(i)) & Chr$(10) & _ "Panel.Tag(" & Str$(i) & ") = " & Str$(Panel.Tag(i)) Panel.SetCaption(i, sString) Next Statusbar.Panel(0).Caption = " If you see... 1 Parent and 4 custom components (each with it's own handle)... then there's your proof that this does work." End Sub