Hi Jose,
I notice that you chose to use the Window extra bytes and GetWindowLong to implement the CWindow Userdata functions. I have already run into a scenario where it would be more advantageous for me to be able to store data in pWindow prior to the window being created (and thus the extra bytes allocated via CWindow_RegisterClass).
Not sure if you would be willing to change the design choice for this feature at this point? Maybe allocate a 10 element Instance array within the CWindow class rather than relying on the hWnd? Maybe you had other reasons for using the window extra bytes approach?
If there are changes to be made, now it's the time, because it isn't going to break any code. I just wanted to mimic the DDT statements because many people are used to them, but it is a rather primitive mechanism. With the latest changes that I have made, you can add unlimited named object references to a collection. If you add a reference to a class, this means that you have access to all the data you want to store in the class and all the methods implemented to manage it. Quite more powerful than a pointer to an UDT or something like that.
So, i will change it to use an array. Feel free to use them for your purposes. Are 40 bytes the right amount? Do you want more or less?
Thanks Jose - I was thinking last night that maybe I should INHERIT your CWindow class as a base class for a new FireFly Window class? That way I could add some very specific FireFly methods and properties that would make code generation in FF4 better. For example, for every form/control in FF3, I create a block of memory to hold information like fore/back color, brush, font, etc. If I INHERIT your class then I simply need to extend the new class with a few properties to hold this type of information.
Also by INHERIT, then I do not have to screw up your CWindow class at all. :)
What do you think? Would that be a better approach? I have not used INHERIT and base classes, etc, yet so it would be "fun" to try if it is a feasible approach.
Otherwise, 40 bytes is more than enough for me. Actually, 4 bytes (room for a pointer) would be enough.
Quote
Otherwise, 40 bytes is more than enough for me. Actually, 4 bytes (room for a pointer) would be enough.
Ok. 4 bytes then.
Quote
For example, for every form/control in FF3, I create a block of memory to hold information like fore/back color, brush, font, etc. If I INHERIT your class then I simply need to extend the new class with a few properties to hold this type of information.
For each control? No. At most, for each form. Each instance of CWindow is what you call a form, not a control. You create controls calling the AdXXX methods of CWindow. There are no individual classes for each control, Ã la MFC.
Paul,
Just me two cents.
Why not use CWindow as a start for a cFFWindow and not use inheritance.
This is a note I sent to Jose awhile ago.
In my Ubx project I am wrapping wxWidget's with c++ classes but I am offering two ways to create the Windows and controls. One is similar to yours but with polymorphism I am able to have an alternative create method with no parameters. I set default values in the constructor and use properties to change these.
I so much prefer this method as I can leave out any or all parameters and not have to worry about the calling order.
I believe you could do the same with all OPTIONAL parameters. Did you, or possibly will you entertain offering this feature?
James
Paul,
There is not need to inherit from anything unless you want to add additional methods or override existing ones, but there is not problem in doing it.
Regarding controls, it's another matter. You will need to write classes for each control. This can be made simple if you're willing to continue using the API to manage them, or very laborious if you want to do everything with the dotted syntax.
The "simple" way:
The class
' ========================================================================================
' CAfxButton class
' ========================================================================================
CLASS CAfxButton
INSTANCE m_hwnd AS DWORD
INSTANCE m_bstrName AS WSTRING
' ....
' More instance variables
' ...
CLASS METHOD Create
END METHOD
CLASS METHOD Destroy
DestroyWindow m_hwnd
OutputDebugString "Button destroy"
END METHOD
INTERFACE IAfxButton : INHERIT IAutomation
PROPERTY GET Name () AS WSTRING
PROPERTY = m_bstrName
END PROPERTY
PROPERTY SET Name (BYVAL bstrName AS WSTRING)
m_bstrName = bstrName
END PROPERTY
METHOD Add (BYVAL pWindow AS IWindow, BYVAL hParent AS DWORD, BYVAL cID AS LONG, BYVAL bstrTitle AS WSTRING, _
BYVAL x AS LONG, BYVAL y AS LONG, BYVAL nWidth AS LONG, BYVAL nHeight AS LONG, _
OPTIONAL BYVAL dwStyle AS DWORD, BYVAL dwExStyle AS DWORD, BYVAL pWndProc AS DWORD, BYVAL bNoScale AS LONG) AS DWORD
m_hwnd = pWindow.AddButton(hParent, cID, bstrTitle, x, y, nWidth, nHeight, _
dwStyle, dwExStyle, pWndProc, bNoScale)
METHOD = m_hWnd
END METHOD
' ....
' More methods/properties
' ...
END INTERFACE
END CLASS
' ========================================================================================
' ========================================================================================
' Gets an object reference to the specified button.
' ========================================================================================
FUNCTION CAfxButton_GetObject (BYVAL hwnd AS DWORD, BYVAL bstrName AS WSTRING) AS IAfxButton
LOCAL pWindow AS IWindow
LOCAL vButton AS VARIANT
LOCAL pButton AS IAfxButton
pWindow = CWindow_GetObjectFromWindowHandle(hwnd)
IF ISNOTHING(pWindow) THEN EXIT FUNCTION
vButton = pWindow.GetObject(bstrName)
pButton = vButton
FUNCTION = pButton
END FUNCTION
' ========================================================================================
The form creation
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS DWORD, BYVAL hPrevInstance AS DWORD, BYVAL lpszCmdLine AS WSTRINGZ PTR, BYVAL nCmdShow AS LONG) AS LONG
LOCAL pWindow AS IWindow
pWindow = CLASS "CWindow"
IF ISNOTHING(pWindow) THEN EXIT FUNCTION
' // Create the main window
' // Note: CW_USEDEFAULT is used as the default value When passing 0's as the width and height
pWindow.CreateWindow(%NULL, "Generic CWindow Program", 0, 0, 0, 0, 0, 0, CODEPTR(WindowProc))
' // Center the window
pWindow.CenterWindow
' // Create a new instance of the CAfxButton class
LOCAL pButton AS IAfxButton
pButton = CLASS "CAfxButton"
IF ISOBJECT(pButton) THEN
' // Important: Give it an unique name
pButton.Name = "Button1"
' // Add the button to the form
pButton.Add(pWindow, pWindow.hwnd, %IDC_BUTTON1, "Button 1", 100, 100, 75, 23)
' // Add the button to the collection
pWindow.AddObject(pButton.Name, pButton)
' // We no longer need this reference
pButton = NOTHING
END IF
' // Default message pump (you can replace it with your own)
pWindow.DoEvents(nCmdShow)
END FUNCTION
' ========================================================================================
the window procedure
' ========================================================================================
' Main callback function.
' ========================================================================================
FUNCTION WindowProc (BYVAL hwnd AS DWORD, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG
' // Process window mesages
SELECT CASE uMsg
CASE %WM_COMMAND
SELECT CASE LO(WORD, wParam)
CASE %IDCANCEL
' // If the Escape key has been pressed...
IF HI(WORD, wParam) = %BN_CLICKED THEN
' // ... close the application by sending a WM_CLOSE message
SendMessage hwnd, %WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
CASE %IDC_BUTTON1
LOCAL pButton AS IAfxButton
pButton = CAfxButton_GetObject(hwnd, "Button1")
' Do something with it
END SELECT
CASE %WM_DESTROY
' // End the application
PostQuitMessage 0
EXIT FUNCTION
END SELECT
' // Pass unprocessed messages to Windows
FUNCTION = DefWindowProc(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
Notice that in the form creation, the button object reference is added to a collection with a unique name. This is to keep it alive without having to make pButton global. Somebody has called me pedantic in another forum for not liking globals, but this is optional, so if you like them declare it as global and don't call AddObject.
' // Add the button to the collection
pWindow.AddObject(pButton.Name, pButton)
If you are pedantic like me, then to retrieve an object reference from the collection, you will have to call CAfxButton_GetObject.
LOCAL pButton AS IAfxButton
pButton = CAfxButton_GetObject(hwnd, "Button1")
If you want to kill the button, you will have to call
pWindow.RemoveObject("Button1")
or set the pButton variable to NOTHING if you are using globals.
If you like the OOP style, you can implement as many properties as you wish in the CAfxButton class, e.g.
PROPERTY GET hParent() AS DWORD : PROPERTY = m_hParent : END PROPERTY
PROPERTY SET hParent(BYVAL hParent AS DWORD) : m_hParent = hParent : END PROPERTY
PROPERTY GET x() AS LONG : PROPERTY = m_x : END PROPERTY
PROPERTY SET x(BYVAL x AS LONG) : m_x = x : END PROPERTY
etc.
You get the idea, don't you?
You can also assign default values in the Create method, e.g.
CLASS METHOD Create
m_width = 75
m_height = 23
...
END METHOD
You will set all the properties and then call an Add method with few or none parameters.
So let's try with a button. Tell me which data you want to store in the class and I will write it for testing purposes.
Keep in mind that we have to use wrappers such CAfxButton_GetObject because Windows passes window handles to the callback procedures, not object references.
Hi Jose,
I have been thinking about this all afternoon and testing some code. Actually, I think that I will stick with my original method of code generation - that is, store a pointer in a Property (GetProp, SetProp) to a dynamically allocated bit of memory for each Form and Control. I retrieve that pointer and instantly get the Form/Control related data that I need. That method allows me to keep FF specific data apart from your CWindow class. I think that writing classes for each individual control would be overkill and over complicate your classes.
Who knows, by tonight, I may changed my mind again.... :) :)
Quote from: James Fuller on April 30, 2011, 10:39:48 AM
Why not use CWindow as a start for a cFFWindow and not use inheritance.
Hi James,
At this time, I would rather simply use Jose's class rather than try to develop a similar class in parallel to Jose's development. I imagine that the CWindow class will expand and evolve over time and having that complexity limited to one developer (Jose) may be easier than me trying to incorporate his additions/deletions to the class in my class every time a change is made.
BTW, how is the development of your compiler product coming along? You are also doing a lot of work in BCX these days too, right?
Quote
I think that writing classes for each individual control would be overkill and over complicate your classes.
I never write classes for the sake of it. I wrote CWindow because it simplifies GUI creation, makes it easier to integrate ActiveX controls and takes care of unicode and High DPI. But to wrap every control is insane and unneeded. I have wrapped image lists, fonts and brushes, and the Open/Save file dialogs.
With the new CAfxFileDialog class you can do:
LOCAL pofd AS IAfxFileDialog
pofd = CLASS "CAfxFileDialog"
IF ISNOTHING(pofd) THEN EXIT SUB
pofd.DefaultFolder = CURDIR$
pofd.FileName = "*.BAS;*.INC"
pofd.DefaultExtension = "BAS"
pofd.Filter = CHR$("PB Code Files (*.BAS)", 0, "*.BAS", 0) & _
CHR$("PB Include Files (*.INC)", 0, "*.INC", 0) & _
CHR$("PB Template Files (*.PBTPL)", 0, "*.PBTPL", 0) & _
CHR$("All Files (*.*)", 0, "*.*", 0)
pofd.Options = %OFN_EXPLORER OR %OFN_FILEMUSTEXIST OR %OFN_ALLOWMULTISELECT
IF pofd.ShowOpenDialog THEN
LOCAL pFiles AS IPowerCollection
LOCAL vFile AS VARIANT
pFiles = pofd.Files
? "Selected path: " & pofd.SelectedPath
FOR EACH vFile IN pFiles
? VARIANT$$(vFile)
NEXT
END IF
But when a class doesn't offer any advantage, I write a function. This is the more recent:
' ========================================================================================
' Returns a collection of the names of all files within a folder.
' - bstrFolder: Folder path.
' Return Value: An object reference to the collection.
' Usage example:
' LOCAL pFileNames AS IPowerCollection
' pFileNames = AfxGetFileNames("C:\windows\System32")
' LOCAL vFileName AS VARIANT
' FOR EACH vFileName IN pFileNames
' MSGBOX VARIANT$$(vFileName)
' NEXT
' ========================================================================================
FUNCTION AfxGetFileNames (BYVAL bstrFolder AS WSTRING) AS IPowerCollection
LOCAL fso AS IFileSystem
LOCAL pFolder AS IFolder
LOCAL pFiles AS IFileCollection
LOCAL pFile AS IFile
LOCAL pEnum AS IEnumVARIANT
LOCAL vItem AS VARIANT
LOCAL celtFetched AS DWORD
LOCAL idx AS LONG
LOCAL bstrName AS WSTRING
LOCAL vName AS VARIANT
LOCAL pFileNames AS IPowerCollection
' // Create an instance of the FileSystemObject
fso = NEWCOM "Scripting.FileSystemObject"
IF ISNOTHING(fso) THEN EXIT FUNCTION
' // Create an instance of the collection
pFileNames = CLASS "PowerCollection"
IF ISNOTHING(pFileNames) THEN EXIT FUNCTION
' // Get a reference to the IFolder interface
pFolder = fso.GetFolder(bstrFolder)
IF ISNOTHING(pFolder) THEN EXIT FUNCTION
' // Get a reference to the IFileCollection interface
pFiles = pFolder.Files
IF ISNOTHING(pFiles) THEN EXIT FUNCTION
' // Get a reference to the standard enumerator
pEnum = pFiles.NewEnum_
IF ISNOTHING(pEnum) THEN EXIT FUNCTION
DO
' // Enumerate the collection
pEnum.Next 1, vItem, celtFetched
IF celtFetched = 0 THEN EXIT DO
pFile = vItem
INCR idx
bstrName = pFile.Name
vName = bstrName AS WSTRING
pFileNames.Add(FORMAT$(idx), vName)
LOOP
FUNCTION = pFileNames
END FUNCTION
' ========================================================================================
There is nothing wrong in mixing COM and procedural code.
Quote from: TechSupport on April 30, 2011, 05:07:12 PM
Hi James,
BTW, how is the development of your compiler product coming along? You are also doing a lot of work in BCX these days too, right?
I'm concentrating on putting together a Bcx 6.6, MinGW, RadAsm3 package right now . Pretty complete c++ translation with the latest release.
James
QuoteWho knows, by tonight, I may changed my mind again.... :) :)
If you continue to improve FF and differentiate/enhance the product by the best available techniques/technology available, nobody will catch you. What matters to me is that I can benefit and gain whatever competitive advantages I can - be it time, features, or GUI eye candy. If using a different set of api's can do that, go for it. I've put my project on hold until I see FF4 - heck, I'll even test it for you.
Rick Kelly