Multi form application message handling.

Started by Francisco Dom, January 07, 2015, 03:14:08 PM

Previous topic - Next topic

Paul Squires

#15
Hi Fran,

Sorry it took so long to respond. Life got in the way :)

I have attached an extremely simplistic project to demonstrate one way to (hopefully) solve your problem.

Here are a few things that I did that you should pay attention to:

- I am using Firefly's built in GraphicControl.
- I am using GDI+ to do the drawing rather than PB's GRAPHIC commands. To accomplish this I am using Jose's GDI+ classes. You simply need to #INCLUDE Once "CGDIPlus.inc" at the top of the source code (or in FF_APPSTART). Jose's GDI+ Help file can be found at: http://www.planetsquires.com/protect/forum/index.php?topic=3597.0
- I am using a simple TYPE structure to hold the measurement information and then dynamically creating a block of memory to represent that TYPE (using FF_MemAlloc).
- I am using the PB functionality to "DIM...AT". This is a powerful technique that allows me to overlay the dynamically created memory block with whatever structure I wish. Accessing TYPE elements is then very easy.
- I create the popup/resizable frmGraphics Form (non-modal) and pass the memory block handle to WM_CREATE via the UserData parameter. I store that memory handle in the Form for later use via the WinAPI functions SetProp, GetProp and RemoveProp.
- The frmGraphics Form contains one GraphicControl that is automatically resized using the "ResizeRules" property.
- The GraphicControl is set to "Stretchable" so that when the Form is resized, the graphic will also resize proportionally.
- You need to use the GetDlgItem api to retrieve the GraphicControl HWND handle rather than using FF's auto created HWND' (via the F4 window). This is because multiple instances of that control can exist at any one time.

Please let me know if any of this is unclear. The program does a LOT in a very few number of lines. Using dynamically created memory is probably not very "BASIC-like" but it certainly simplifies the design. No need to have global arrays or classes/collections, etc.

Paul Squires
PlanetSquires Software

David Kenny

Francisco,

Quote from: Francisco Domínguez Latorre on January 08, 2015, 12:24:35 PM
1.- At design time it is unknown how many graphs are needed.
2.- The graphs should resizable.
3.- The graphs should respond to WM_CONTEXTMENU messages in order to the user can save the graph plots or measurement data to disk.

Paul addressed the first two items in his demo.  Inserting the code below into the bottom of the frmGraphic (from Paul's demo project in his last post) will show how to implement item number three in FF.  Although you could use a WndProc, as you suggested, I hope you can see that FF can do it much easier with built-in functionality.

Enum ContextMenuID
    Save = 1501
    Print
End Enum

'--------------------------------------------------------------------------------
Function FRMGRAPHIC_WM_COMMAND ( _
                               hWndForm     As Dword, _  ' handle of Form
                               hWndControl  As Dword, _  ' handle of Control
                               wNotifyCode  As Long,  _  ' notification code
                               wID          As Long   _  ' item, control, or accelerator identifer
                               ) As Long
   Select Case wID
      Case %ContextMenuID.Save          'Context menu selection
          MsgBox "Save Data for:" &  Str$(GetProp(hWndForm,"MEMHANDLE"))
      Case %ContextMenuID.Print
          MsgBox "Print Data for:" & Str$(GetProp(hWndForm,"MEMHANDLE"))
   End Select
End Function


'--------------------------------------------------------------------------------
Function FRMGRAPHIC_WM_CONTEXTMENU ( _
                                   hWndForm      As Dword, _  ' handle of Form
                                   xPos          As Long,  _  ' x-coordinate of cursor
                                   yPos          As Long   _  ' y-coordinate of cursor
                                   ) As Long
    Local hPopup    As Long
                                       
    Menu New PopUp To hPopup

    Menu Add String, hPopUp, "Save Data"  , %ContextMenuID.Save , %MF_ENABLED
    Menu Add String, hPopUp, "Print Data" , %ContextMenuID.Print, %MF_ENABLED
    TrackPopupMenu hPopup,%TPM_LEFTALIGN Or %TPM_RIGHTBUTTON, _
        xPos, yPos, 0, hWndForm, ByVal 0

End Function


We didn't do all your homework... :) just enough to get over the learning curve (hopefully anyway).

David

Francisco Dom

Paul,

Took so long to respond??? Are you joking? Next day answer with custom code...Yes! You are joking.

I would like to make some comments. I am sorry because it is a bit, how to say, boring, dense,...?

1.- I see you dimensioned Measurement(0) both in the frmMain and frmGraphic with "Dim Measurements(0) As MEASUREMENT_TYPE At hMemHandle"
What is the scope of this variable? Local to each function?

2.- SetProp API call is very interesting, it looks it allows to store some user data into any window without declaring or using Global variables. In this case the Window Property "MEMHANDLE" is created in the Main Window which stores the MemHandle of the Graphic Data Structure allocated in memory. Clever approach!! So many things to learn....

3.- One of the most important lesson I learned from your code is that frmGraphic_Show(hWndParent,ShowModalFlag, UserData) instantiates a new window of an already created form in the Form Editor and returns the handle of the new created window, this makes available to the programmer the message loop function of all the created new windows. I think it doesn't matter if I use GDI+, PB Graphic functions or OpenGL, I can respond to events in every new window created.

This frmNAME_Show functions are too important to trust on my memory and digging in old code or in the help file to remember it exists and how to use it when I need it. As a ocasional programmer I need a way to quickly get an overview of the most common functions.

I will create and entry in the Functions Library manager with big red blinking font....Hell!! I cannot use big red blinking font in the Function Library Manager. ;)

IMHO, the text in the FF Help File was not enough detailed because  it states that:

"Every Form in FireFly has code generated that you can call to display that Form" and "For example, to show a Form called "MyForm", you would call the following from within your code:"
Because of this both statements I thought this kind of functions will only DISPLAY an already created form although I should had a more detailed look at what is also said: "The function will return the Window handle of the newly created Form."

Anyway as a novice programmer in FF and being a non native English speaker all of this was not enough clear for me. Luckily it has been perfect clarified thanks to your code.

4.- Another important think I learned is that GDI+ is too much for me. During this days I have been reading the GDI+ Help File from Jose Roca and I was overwhelmed...about 600 functions, 40 classes, wrappers, etc.... What a huge work from Jose Roca!!!
I am still getting used to PB and FF with all its functions, starting to use some API functions, etc, so sincerely I don't think I am ready for GDI+ at this moment.

In fact, as I am simply need 2D plots, without alpha channels, bezzier curves, etc and because PB DDT Graphic commands are so simple to use and perform so well I insisted on integrating them in the FF application.

Regarding your sample code with GDI+ you are resizing the Graphic Control but you are not replotting the "data". When you make the window bigger, the lines also get thicker, it's like a zooming on a low resolution BMP.
I have been trying to replot the data on the WM_SIZE but I have not been able even to "clear" the plot before drawing the new data with GraphCtxClear.

5.- Regarding your comments about GetDialogItem, in the demo I created I stored the Graphic Handle in a form property for every new window created but Is a good point to remember in the future. Thanks for the lesson.

6.- After severals failed attempts to use DefWndProc, WndProc and CallWndProc I realized that all of that is managed by FireFly Subclassing functions. That is the way I decided to "intercept" the messages from instances of the same form.
Regarding this I read somewhere that all the instances of the same window class share the same Customized Window Procedure and I checked that is true, after printing on every created form the CODEPTR(GraphicFunctionProc) defined by me.

Who manages all the details when the GraphicFunctionProcedure is defined on every form created on runtime? FF Code Generator or PB Compiler?
What will happen when I declare the function inside an object of a user defined class?





Robert,

1.- Thanks for the information about your program and the source code. I asked about RF because of the screen capture I saw in your FF forum post http://www.planetsquires.com/protect/forum/index.php?topic=2020.0 but after compiling your source code I realized it is not the same.
The screen capture in the mentioned post show it is "RF Scan" from "EV". Can you give me a link to that software or company that created it?

2.- After looking inside your code I saw you used some API calls (MoveTo, LineTo) to draw on the form, managed by yourself device contexts, etc, etc....That is too advanced programming for me.
After some time I realized your code was out of my reach although the graphic result behavior was exactly what I needed.
This make me focus on "integrating" DDT Graphic Functions in FireFly as my main goal.

3.- One of the reason for me to move to FireFly was the complication of form creation in PowerBasic. I tried PBForms with PB and is good for small applications but when you want to create and "advanced" user interface things start to get more and more difficult to manage for a beginner comming from VB4 because of all that message loop stuff, callbacks, etc.
Firefly allows me to create a control and on design time define its size an position on the form before compiling and see how it looks. That is why I want to have a control or at least a container for a graphic control that is "watchable" in the form editor.





David,

1.- as now the approach is slightly different because I managed to embed a PB graphic control insisde a FF Custom Control in a form with buttons, labels, etc, I can use buttons to trigger several different actions, anyway as I have learned how to intercept the window messages in a "runtime" created window it is easy to respond to the context messages and to others.

In fact, now the graphic is resized and replotted responding to WM_SIZE but it is not when I maximize the window. I'll have to investigate what is the message sent in that case.

2.- Well...you didn't do my homework but you gave me the pencil, paper and more than a starting point. I hope the result is at the same level as the given help.  ;)





I would like to share with all of you and with the FireFly community what I learned thanks to all of you and I will appreciate your comments, corrections or suggestions about the source code attached to this mail.


Finally I would like to apologize for this so extensive post and once again express my gratitude for your help and your time

Best regards.

Fran

Robert Eaton

Francisco,
The RF scan program is mine. In that program the GUI was written in VB6 but the drawing routines were done with a PB dll (data was passed to a VB6 picturebox).
So my goal was with similar, future programs, to be able to do that without the VB6 component, which I did.

In the final version I support up to 32 curves (more than that gets too cluttered).
You fill an array with the level and frequency points for each curve as well as the curve color and line width for each curve.
These programs are of course used at my work. I work for Bosch, and EV (ElectroVoice), is one of the brands.)



Paul Squires

Quote from: Francisco Domínguez Latorre on January 13, 2015, 01:59:52 PM
1.- I see you dimensioned Measurement(0) both in the frmMain and frmGraphic with "Dim Measurements(0) As MEASUREMENT_TYPE At hMemHandle"
What is the scope of this variable? Local to each function?
The scope is local because I used DIM inside of a sub/function.

Quote
2.- SetProp API call is very interesting, it looks it allows to store some user data into any window without declaring or using Global variables. In this case the Window Property "MEMHANDLE" is created in the Main Window which stores the MemHandle of the Graphic Data Structure allocated in memory. Clever approach!! So many things to learn....
There are many things that can make programming easier for you if you use the WinAPI directly. Never be afraid to use it regardless of what the fear mongering anti-WinAPI people tell you :)

Quote
4.- Another important think I learned is that GDI+ is too much for me. During this days I have been reading the GDI+ Help File from Jose Roca and I was overwhelmed...about 600 functions, 40 classes, wrappers, etc.... What a huge work from Jose Roca!!!
I am still getting used to PB and FF with all its functions, starting to use some API functions, etc, so sincerely I don't think I am ready for GDI+ at this moment. Using the FireFly GraphicControl with GDI functions is easy to do as well.

If GDI+ is too overwhelming then you still have the option of using the simpler GDI functions. I expect that the GDI functions is what the PB GRAPHIC statements use. Jose has a help file for thos functions as well! Check out his GDI.chm help file.

Quote
Regarding your sample code with GDI+ you are resizing the Graphic Control but you are not replotting the "data". When you make the window bigger, the lines also get thicker, it's like a zooming on a low resolution BMP.
I have been trying to replot the data on the WM_SIZE but I have not been able even to "clear" the plot before drawing the new data with GraphCtxClear.
I never focused on the graphic plotting but simply made the graphic resizable. You're right, I should have used an approach to clear the graphic and then re-plot the graphic based on the new size.

Quote
5.- Regarding your comments about GetDialogItem, in the demo I created I stored the Graphic Handle in a form property for every new window created but Is a good point to remember in the future. Thanks for the lesson.
I downloaded your sample code project and you have made this error several times in your code GraphicSubClassProc. You will notice that when you create several of your resizable graphic forms (and then use your mouse to resize them) that the wrong controls will be acted on. For multiple instanced forms you should *never* rely on the FireFly generate HWND_* variables. You should always use the GetDlgItem(HWND_*, IDC_*) approach.

I also find it very confusing to add a GRAPHIC WINDOW to a CUSTOMCONTROL of a FORM. Too many sub-windows to keep track of. :) I'm thinking that it would be easier for you to bypass the CustomControl altogether and simply add the GRAPHIC WINDOW to the FORM directly in the Form's WM_CREATE??? Or better yet, just use the FF Graphiccontrol with GDI functions  :)

Quote
Who manages all the details when the GraphicFunctionProcedure is defined on every form created on runtime? FF Code Generator or PB Compiler?
What will happen when I declare the function inside an object of a user defined class?

I am not 100% sure I understand the question. Subclassing simply intercepts messages destined for your window prior to it being sent to the default message procedure for that window. In the subclass, you can modify what you want to happen prior to the window/control receiving the message. In most cases you will eventually want the message to be directed to the default procedure otherwise Windows itself may act funny. That's all the FireFly subclass wrappers do: It allows you to specify a function where you can put code to act on messages prior to them being sent to the default message procedure of window. FireFly generates all of the necessary code.

Not sure if you can define the GraphicFunctionProcedure function in a Class. Does CODEPTR work on a function within a Class? Not sure. If you can get the address of the function (via CODEPTR) then the Class approach should work. I haven't tested this though.


Paul Squires
PlanetSquires Software

José Roca

> What will happen when I declare the function inside an object of a user defined class?

That won't work.

Methods in a class have a hidden parameter, generally know as "this", that is the address of a pointer to the class virtual table. Windows will call the method without passing the "this" parameter and it will crash.

In the past years, some PBer's have tried to do it and, of course, they have failed miserabily.

Francisco Dom

#21
Paul,

your comments are much appreciated.

QuoteThere are many things that can make programming easier for you if you use the WinAPI directly. Never be afraid to use it regardless of what the fear mongering anti-WinAPI people tell you :)
You are right. I am realizing about that, but it is a hard way because so many functions to learn besides the ones of the language in use. Good advice.

QuoteIf GDI+ is too overwhelming then you still have the option of using the simpler GDI functions. I expect that the GDI functions is what the PB GRAPHIC statements use. Jose has a help file for thos functions as well! Check out his GDI.chm help file.
Yes, I also read about GDI, looks simpler and more than enough for what I need. The problem are not the functions by itself but the uses of several classes, initialization and all that stuff. I recently discovered the wonderful use of classes and how can ease my work but one thing is write a class with a couple of properties and methods and a different thing is directly jump to GDI+.
I'll give GDI an opportunity for this application.

QuoteI downloaded your sample code project and you have made this error several times in your code GraphicSubClassProc. You will notice that when you create several of your resizable graphic forms (and then use your mouse to resize them) that the wrong controls will be acted on. For multiple instanced forms you should *never* rely on the FireFly generate HWND_* variables. You should always use the GetDlgItem(HWND_*, IDC_*) approach.
Paul, initially I follow you suggestion of using GetDlgItem but I always got "0" as the handle of the Custom Control. I don't know if the reason is because it is "associated" with the  PB Graphic Dialog/Control but it didn't work. Have a look, this is the code:


   

hCustomControl=GetDlgItem (hWndControl,IDC_FORM2_CUSTOMCONTROL1)
   Select Case wMsg
      Case %WM_SIZE
           ztrace "Entering into message loop."
           hGraphic=GetProp(hWndControl,"HGRAPHIC")
           Graphic Clear 
           'Control Kill hCustomControl,IDC_FORM2_CUSTOMCONTROL1  'IMPORTANT!!! The Control Owner is not the Form but the CustomControl handle.
           Control Kill HWND_FORM2_CUSTOMCONTROL1,IDC_FORM2_CUSTOMCONTROL1  'IMPORTANT!!! The Control Owner is not the Form but the CustomControl handle.
           
'****************************
'* HANDLE OF CUSTOMCONTROL GIVES 0 IF GOT WITH GETDLGITEM
'****************************
            ztrace "Custom Control Handles:"+str$(hCustomControl)+Str$(HWND_FORM2_CUSTOMCONTROL1)


QuoteI also find it very confusing to add a GRAPHIC WINDOW to a CUSTOMCONTROL of a FORM. Too many sub-windows to keep track of.
Yes, it is very confusing also for me but it is the way you suggested in this forum some time ago before the FF Graphic Control was included in FF. Regarding hadles I have no idea about what is really happening inside the program with this approach.

QuoteI'm thinking that it would be easier for you to bypass the CustomControl altogether and simply add the GRAPHIC WINDOW to the FORM directly in the Form's WM_CREATE???
The problem is that as I come from VB4 I am not used to create controls in a form in runtime. I like to design the form in the form editor and knowing exactly the phisical space they need and place the buttons, labels and other controls in the proper positions.
When you said "GRAPHIC WINDOW" you mean to use GRAPHIC WINDOW NEW PB function? I asked this because that funcion creates a NEW window not a child window of the current form.

QuoteOr better yet, just use the FF Graphiccontrol with GDI functions
I agree with you, this will be the way. I will move to SDK programming even before having enough skills in Windows programming  :)

I read somewhere that GDI+ is slower than GDI because starting with Win7 GDI is hardware accelerated while GDI+ is not. Do you think it is true? It doesn't matter for my plots, it is just curiosity.

Anyway independently of if I use PB Graphic functions, GDI or GDI+ I am learning so much from this post and forum thanks to FireFly.

QuoteI am not 100% sure I understand the question. Subclassing simply intercepts messages destined for your window prior to it being sent to the default message procedure for that window. In the subclass, you can modify what you want to happen prior to the window/control receiving the message. In most cases you will eventually want the message to be directed to the default procedure otherwise Windows itself may act funny. That's all the FireFly subclass wrappers do: It allows you to specify a function where you can put code to act on messages prior to them being sent to the default message procedure of window. FireFly generates all of the necessary code.
What I mean is that I declared the function in the Form2 and I created several instances of Form2.
As all the windows of a registered class share the same window procedure and as I have (or at least is what I think) the GraphicFunctionProcedure existing in every Form2 window.
Is the GraphicFunctionProcedure function existing for each window although only the address of that function is associated to the class window Form2 belongs to?
Which of those addresses of the GraphicFunctionProcedure is associated with the Form2 Window Class? The first time I subclass the Form2?  The last time?

I am trying to guess what will happend when the function is declared and called in every instance of a Form. Not because I want to do that but because I would like to learn the internals of Windows/FireFly/Compiler.

QuoteNot sure if you can define the GraphicFunctionProcedure function in a Class. Does CODEPTR work on a function within a Class?
I will try this morning as a learning task.
Is CODEPTR function getting the address of the function at compiling time or in running time?
What is happening in my application? Because in every Form2 instance I get exactly the same GraphicFunctionProcedure address.

Could you be so kind to have a look inside the code in order to know why I am getting a "0" with hCustomControl=GetDlgItem (hWndControl,IDC_FORM2_CUSTOMCONTROL1)

Thank you so much for your time and support.

Best regards.

Fran

Francisco Dom

#22
Jose,

thanks for answering.
I would like to take the chance to express you my gratitude for all your support and contributions to PB and FF with all those WinApiHeader, GDI and GDI+ stuff. And all for free!!!
I am very proud we have in Spain so skilled people that offer they help without any other interest.


Quote from: Jose Roca on January 13, 2015, 06:58:46 PM
> What will happen when I declare the function inside an object of a user defined class?

That won't work.

Methods in a class have a hidden parameter, generally know as "this", that is the address of a pointer to the class virtual table. Windows will call the method without passing the "this" parameter and it will crash.

In the past years, some PBer's have tried to do it and, of course, they have failed miserabily.

So, what will be the path? Maybe defining the function in the main form or in a module and passing the address of the GraphicWndProc to a property in the class?
If several objects of the same class are instantiated what will happen if I set the property for each object?

Let suppose that each object has its own thread for both taking measuring from the Test Instruments and for processing messages of each window created in every class objects. In this case every window will need the address of the GraphicWndProc. What is the proper way to do this?

Best regards.

Fran

David Kenny

Francisco,

I modified your second project post to get rid of the PB DDT graphics.  I think you will like the results.

Quote from: David Kenny on January 07, 2015, 10:20:43 PM
If the user can open multiple forms, then it might make sense to use FF's new feature that enables multiple instances of one form.  .
Sorry, this was referring to a new feature that was implemented in the FF Dot syntax preview a while back. We will have to wait until it is released to take advantage of it.  Right now, the GetDlgItem method of getting the control handles is a good way of handling multiple instances of a FF form.  If you need Static variables in this context, you will need to take care of that yourself also.  I have a method I use if anyone is interested.

David