Trying to break my VB habit...

Started by Robert Eaton, November 10, 2009, 01:44:40 PM

Previous topic - Next topic

Robert Eaton


I’m essentially a “hobby” programmer. I mostly write some little utilities for use at work. I usually use VB along with a PB dll. I previously purchased FF2, but never really used it. When I heard FF3 was coming out, I wanted to give it another try. I realize that VB automatically takes care of some things that I will have to handle myself. So here is my first challenge in the learning curve.



The attached screen shot is from a VB program I wrote to do some RF testing and I’m working on duplicating the graph drawing routine in FF3. The original graph is done with API polylines and textout with a PB dll that draws into a VB picturebox control.  (I know now that the VB picturebox is not a standard Windows control.) My first attempt was to do something similar by trying to draw lines into a FireImage control. (I posted a question about this Code and Programming Examples forum, but never got a reply.) Although I did eventually get that to work partially, I’m thinking that a better approach is to simply draw the graph on the form.

I did a simple test where I draw a solid rectangle on a form and then draw some line and text on top. This works, but of course flicker when resizing the form is pretty horrible. So a couple of questions:

1)   Is drawing the graph on the form the best approach? (As opposed to drawing inside control.)

2)   I have played with the class controls for the form but just get different flavors of bad as far as the redraw issues. I’m guessing some kind of double-buffer thing is needed. Can anyone point me?

Thanks in advance.

Haakon Birkeland

1) Probably not. I can't technically argue why, but previous experience with having the whole form, with all it's controls, redrawn is asking for issues with flickering etc.

2) My exact thought while reading the initial way of getting this done. Paint the story backstage and flick it to display sounds like the best approach. Unfortunately this is beyond my personal experience ...
Haakon 8o)

Paul Squires

#2
By far the best (and most likely, easiest) solution is to create your own custom control for this. You can see custom controls in action by checking out the code for the controls in the "\CustomControls" subfolder.

Take a look at one of the easier controls that I wrote (FireLines). The code is in the FireLines.inc file. Basically, the entire template for a basic control is there. All you need to do is change a few names (like, FireLine of course). You could #INCLUDE your code via the FF_AppStart or even put all of the code into a Module if you wish.

You could go the "long way around" and create a Label or something similar on the screen and then handle the Label's WM_PAINT but that is a little cumbersome. Creating your own dedicated control would be a much more rewarding experience.

I bet that once you get that control working that you will create controls for just about everything! I created them for FF3's tabcontrol, Property ListBox, and even the code editor itself.

Don't be scared off because it is not as hard as you might think. Actually, keep posting your progress here and we'll help every step of the way. I bet that many people would learn a thing or two.

Your custom control should have very little flicker, but if it does then you can add double buffering. Double buffering is a joke to add to your WM_PAINT so don't let that intimidate you at all. I didn't use it in FireLines because I thought that it was a little overkill. It is just a matter of doing the following:

(1) Create a compatible memory device context:
      memDC = CreateCompatibleDC(ps.hDC)

(2)  Create a compatible bitmap that you will do your drawing on: 
        hbit  = CreateCompatibleBitmap(ps.hDC, rc.nRight, rc.nBottom)

(3) Select the bitmap into the memory device context:
        hbit = SelectObject(memDC, hbit)

(4) Do all of your drawing using the memory device context (memDC) rather than the controls original DC (hDC).

(5) When finished drawing, copy the entire memDC over to the hDC in one wicked fast step:
        BitBlt ps.hDC, 0, 0, rc.nright, rc.nbottom, memDC, 0, 0, %SRCCOPY

(6) Delete the objects that you created in order to prevent GDI leaks.
        If hbit  Then DeleteObject SelectObject(memDC, hbit)
        If memDC Then DeleteDC memDC

When doing this kind of stuff, you ALWAYS want to be careful to ensure that you save the original hDC's objects and restore them at the end of your routine prior to deleting any objects (pens, brushes, etc...) that you created while drawing.

You should never delete a pen, brush, etc if it is still selected in a device context. That is why I ALWAYS do a SaveDC and RestoreDC in my code. In some people's code you may see individual variables for hBrushOld or hPenOld, etc... Basically, they are saving each individual aspect of the DC in order to restore them later, I find that cumbersome and potential for errors. It is easier (especially on today's fast and memory flush systems) to save the entire DC with SaveDC and then use RestoreDC at the end and then finally DeleteObject to make the brushes and pens we created disappear.

Please let me know how you make out.  Topics like this are really exciting!!!!!  :)
Paul Squires
PlanetSquires Software

Robert Eaton

Quote from: TechSupport on November 10, 2009, 02:57:37 PM
Take a look at one of the easier controls that I wrote (FireLines).
Easy for you to say ;D
(Thanks for the detailed answer!)

I took a look at the FireLines.inc file, and it makes my head hurt but I want to give it a try.

First Question: Once I have created the modified file and #Included it in a project, how do I add the new control to my form?

Thanks,
Bob

Paul Squires

Hi Bob,

For the FireLines control, all you have to do is call the FireLines function specifying some parameters.

Function FireLines( ByVal hWndParent As Dword, _
                    ByVal CtrlId As Long, _
                    ByVal vLeft As Long, _
                    ByVal vTop As Long, _
                    ByVal vWidth As Long, _
                    ByVal vHeight As Long, _
                    ByVal wStyle As Long, _
                    ByVal wStyleEx As Long _
                    ) Export As Dword             

I would put that in the WM_CREATE of the Form where you want the control to display.

Parameters:
hWndParent = HWND_FORM1    <-- or whatever the Form is you are using
CtrlId = 101   <-- or whatever unique id you want to use
vLeft, vTop, vWidth, vHeight = pretty self explanatory. Actually, I usually set these to 0 and move the control into position in the WM_SIZE handler of the Form.
wStyle = These are WS_ equates used for the CreateWindowEx api call (just make sure to use %WS_CHILD)
wStyleEx = These are the WS_EX_ equates used in the CreateWindowEx api call (optional)

Paul Squires
PlanetSquires Software

Paul Squires

You will notice in the code that there is a TYPE called FIRELINES_DATA. The TYPE is created for each instance of your control in order to maintain "state" information for the control. The TYPE is dynamically created in the WM_CREATE using a call to the HeapAlloc api. All that does is to manually create a block of memory to put your TYPE info in. We store the address to that memory block in the control itself by using the SetWindowLong call.

Whenever we need to access the TYPE we create a pointer (Local ed As FIRELINES_DATA Ptr) and set that pointer to our block of memory. We can then access any parts of the TYPE through out pointer.  e.g.  @ed.IsLIne, @ed.IsVertical, etc...

When you need to "do things to the control" then you send a message to the control. That's what all those equates are for that appear at the beginning of the source code:
%FIRELINES_SETLINE       = %WM_USER + 2048
%FIRELINES_SETVERTICAL   = %WM_USER + 2049
%FIRELINES_SETLINECOLOR  = %WM_USER + 2050
%FIRELINES_SETLINEWIDTH  = %WM_USER + 2051
%FIRELINES_SETLINESTYLE  = %WM_USER + 2052
%FIRELINES_SETROUNDNESS  = %WM_USER + 2053
%FIRELINES_SETSOLIDBACK  = %WM_USER + 2054 
%FIRELINES_SETCTLCOLOR   = %WM_USER + 2056

You send the message to the control using...... the SendMessage api!!!! :)

e.g. (tell the control to create a line)
SendMessage hWndControl, %FIRELINES_SETLINE, 1, 0

In the Windows Procedure (FireLinesProc) you handle the incoming message:

         Case %FIRELINES_SETLINE
            '0:Box, 1:Line
            @ed.IsLine = wParam
            InvalidateRect hWnd, ByVal %Null, %TRUE
            UpdateWindow hWnd

The InvalidateRect and UpdateWindow just tells the control to repaint itself (it sends a WM_PAINT to the control).

All of the real work is done in the WM_PAINT handler of the control.
Paul Squires
PlanetSquires Software

Robert Eaton


Robert Eaton

Quote from: TechSupport on November 10, 2009, 08:09:16 PM
You send the message to the control using...... the SendMessage api!!!!
           
Question: Is there a way to retrieve the current value of those variables in the control?

The control is basically up and running but it looks like I will still need to double buffer it.

Paul Squires

Yes, you get values from the control by using the SendMessage function as well. :)

You define an equate for the message and return the 32-bit back from the SendMessage.

For example, take a look at Borje's RRButton code (RRBUTTON.INC) and you will see various messages that allow the returning of data.

%RBM_GETCORNER     = %WM_USER + 101 'returns button's round rect corner
%RBM_GETRAISED     = %WM_USER + 110 'returns button's flat/raised flag
%RBM_GETLABELMODE  = %WM_USER + 111 'returns label mode, 0=none, 1=flat, 2=raised, 3=sunken
%RBM_GETSHOWFOCUS  = %WM_USER + 112 'returns button's focus rect on/off flag


     Case %RBM_GETCORNER    : Function = @rr.corner    : Exit Function
     Case %RBM_GETRAISED    : Function = @rr.raised    : Exit Function
     Case %RBM_GETLABELMODE : Function = @rr.labelMode : Exit Function
     Case %RBM_GETSHOWFOCUS : Function = @rr.FocusRect : Exit Function

In your code you call it like this:
nCornerValue = SendMessage( hWndControl, %RBM_GETCORNER, 0, 0 )

It works great for returning 32-bit numeric values. To return strings, you can return a pointer to the string and then deal with it.

Having said all that, there is nothing stopping you from using standard Subs or Functions in your custom control code! If your code is going to be in a DLL then all you need to do is Export them. If you are #Including the code in your application then you don't need to export them.

Check out Jose Roca's XP Theme Button code (TB_XPBUTTON.INC) to see this in action. Check out functions like these:
TB_XPButton_SetImagePos
TB_XPButton_SetTextMargin
TB_XPButton_SetToggleState

Paul Squires
PlanetSquires Software

Robert Eaton


Robert Eaton

I have my grid control up and running but there is still flicker problems so I'm trying to get the double buffer technique working.  Although I did get it to function, there is no improvement so I must be doing something wrong.

Even with all the drawing routines removed, the background of the control flickers when I re-size the main form. (I tried changing the class style redraw settings on the form.) Here is the %WM_PAINT handler form the control with the double buffer routine (and the drawing routines removed).

         Case %WM_PAINT
                   
                  'Get the DC for the parent form
                  hDC = BeginPaint(hWnd, LpPaint)                       
                  SaveDC hDC
                 
                  'Create the solid background       
                  GetClientRect hWnd, tRect
                 
                  memDC = CreateCompatibleDC(LpPaint.hdc)
                  hbit  = CreateCompatibleBitmap(LpPaint.hDC, tRect.nRight, tRect.nBottom)
                  hbit = SelectObject(memDC, hbit)
           
            BitBlt LpPaint.hDC, 0, 0, trect.nright, trect.nbottom, memDC, 0, 0, %SRCCOPY
           
            If hbit  Then DeleteObject SelectObject(memDC, hbit)
            If memDC Then DeleteDC memDC
                 
            RestoreDC hDC, -1
            EndPaint hWnd, LpPaint
           
            Function = 0
            Exit Function


Here is the code on the main from the calls the control and set the size.

Function FORM1_WM_CREATE ( _
                         hWndForm As Dword, _      ' handle of Form
                         ByVal UserData As Long _  ' optional user defined Long value
                         ) As Long


'place the fgrid control on the form
hfgrid = fgrid(hWndForm, 200, 80, 0, 0, 0, %WS_CHILD, 0)



End Function

Function FORM1_WM_SIZE ( _
                       hWndForm      As Dword, _  ' handle of Form
                       fwSizeType    As Long,  _  ' type of resizing request
                       nWidth        As Long,  _  ' new width of client area
                       nHeight       As Long   _  ' new height of client area
                       ) As Long


FF_Control_SetLoc( hfgrid, 80, 25 )

FF_Control_SetSize( hfgrid, nWidth -90, nHeight -35 )

End Function


Any suggestions?

Rolf Brandt

#11
Not sure if this will make much of a difference, but you could replace the code in Form1_WM_Size with this:

MoveWindow hfgrid, 80, 25, nWidth -90, nHeight -35, %TRUE

It's just one API call instead of two functions. Might be a little faster. You might also set the repaint flag from %TRUE to %FALSE to see if that has any effect.
Rolf Brandt
http://www.rbsoft.eu
http://www.taxifreeware.com
I cook with wine, sometimes I even add it to the food.
(W. C. Fields)

Robert Eaton

#12
Rolf,
Thanks for the suggestion, that is more efficient. However, it didn't seem to make much difference in the result.

I have attached the projects if anybody wants to try it. The simple version with just the control and also the full version that has the line drawing routines.

(Fgrid.zip has been updated to remove the %WS_EX_TRANSPARENT which fixes the flicker problem. 12/06/2009)

Paul Squires

Hi Bob,

I haven't looked too closely at your code but you should remove the %WS_EX_TRANSPARENT extended style. I bet you won't have any flicker then. Trust me.  :)


    ' Remove the %WS_EX_TRANSPARENT extended style
    Function = CreateWindowEx( 0, szClassName, "", _
                               %WS_CHILD Or %WS_VISIBLE Or %WS_CLIPSIBLINGS Or %WS_CLIPCHILDREN, _
                               vLeft, vTop, vWidth, vHeight, _
                               hwndParent, CtrlId, _
                               GetWindowLong(hWndParent, %GWL_HINSTANCE), _
                               ByVal 0)

Paul Squires
PlanetSquires Software

Paul Squires

Oh, and congratulations!!! You've done a wonderful job so far in recreating your VB control. Very nice indeed. I did a quick test and there are no GDI leaks at all.

See - building your own control wasn't that hard now was it?  ;)
Paul Squires
PlanetSquires Software