CWindow - UserData Get/Set

Started by Paul Squires, April 29, 2011, 03:42:59 PM

Previous topic - Next topic

Paul Squires

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?
Paul Squires
PlanetSquires Software

José Roca

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?

Paul Squires

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.
Paul Squires
PlanetSquires Software

José Roca

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.

James Fuller

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



José Roca

#5
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.

Paul Squires

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....  :)  :)

Paul Squires
PlanetSquires Software

Paul Squires

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?

Paul Squires
PlanetSquires Software

José Roca

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.

James Fuller

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

Richard Kelly

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