Custom Control 2nd

Started by Christian Weilguny, September 07, 2010, 06:05:43 AM

Previous topic - Next topic

Christian Weilguny

Hi,

I've created a .ctl-File:

#FireFly_Custom_Control#

[Control]
controlname     = WsTextBox
description     = WsTextBox (B4B-Textbox)
author          = Christian Weilguny
copyright       = WeSoft (c) 2010
version         = 1.00
dll_filename    = WsControls.dll
dll_required    = 1
source_filename = WsTextBox.inc
toolbox_bitmap  = Ws_Text.bmp
toolbox_cursor  = Ws_Text.cur
toolbox_tooltip = WsTextBox (B4B-Textbox)
delimiter       = |
uniqueid        = {0B5515F6-A7A9-4279-92F4-76CA1CD2F5E6}
use_loadlibrary = 1
use_initialize_action = 0
initialize_action_designer =
initialize_action_code =
create_action   = WsControls.dll|WSTEXTBOX|WSTEXTBOX|BYVAL LONG //CTRL_PARENT//|BYVAL LONG //CTRL_ID//|BYVAL LONG //CTRL_LEFT//|BYVAL LONG //CTRL_TOP//|BYVAL LONG //CTRL_WIDTH//|BYVAL LONG //CTRL_HEIGHT//|BYVAL LONG //CTRL_STYLE//|BYVAL LONG //CTRL_EXSTYLE//
       

[Property]
name        = name|(Name)
curvalue    = WsTextBox
itemtype    = Edit|1

[Property]
name        = windowstyles|(WindowStyles)
curvalue    =
itemtype    = Styles
       
[Property]
name        = controlindex|ControlIndex
curvalue    = 0
itemtype    = Edit|1

[Property]
name        = locked|Locked
curvalue    = False
itemtype    = Combo
cmbitems    = False|True
equates     = False|True

[Property]
name        = resizerules|ResizeRules
curvalue    =
itemtype    = ResizeRules

[Property]
name        = tag|Tag
curvalue    =
itemtype    = Edit|1

[Property]
name        = tag2|Tag2
curvalue    =
itemtype    = Edit|1

[Property]
name        = text|Text
curvalue    = WsTextBox
itemtype    = Edit|1


[Message]
name          = WSTEXTBOX_MSG_CHANGE
declare       = (ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idTextControl As Long) As Long
call          = Fly_nResult = //MESSAGE// (Fly_ControlIndex, hWndForm, @Fly_pNotify.hwndFrom,  @Fly_pNotify.idFrom)
notification  = Notify_Notification
default = True

[Message]
name          = WSTEXTBOX_MSG_SETFOCUS
declare       = (ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idTextControl As Long) As Long
call          = Fly_nResult = //MESSAGE// (Fly_ControlIndex, hWndForm, @Fly_pNotify.hwndFrom,  @Fly_pNotify.idFrom)
notification  = Notify_Notification

[Message]
name          = WSTEXTBOX_MSG_KEYPRESS
declare       = (ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idTextControl As Long, pKeyPressNotify As Dword) As Long
call          = Fly_nResult = //MESSAGE// (Fly_ControlIndex, hWndForm, @Fly_pNotify.hwndFrom,  @Fly_pNotify.idFrom, Fly_pNotify)
notification  = Notify_Notification

[Message]
name          = WSTEXTBOX_MSG_KILLFOCUS
declare       = (ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idTextControl As Long) As Long
call          = Fly_nResult = //MESSAGE// (Fly_ControlIndex, hWndForm, @Fly_pNotify.hwndFrom,  @Fly_pNotify.idFrom)
notification  = Notify_Notification

[Message]
name          = WSTEXTBOX_MSG_CLICKED
declare       = (ControlIndex As Long, hWndForm As Dword, hWndControl As Dword, idTextControl As Long) As Long
call          = Fly_nResult = //MESSAGE// (Fly_ControlIndex, hWndForm, @Fly_pNotify.hwndFrom,  @Fly_pNotify.idFrom)
notification  = Notify_Notification

[Message]
name = CUSTOM



I've created the .dll with the 'WsTextBox'-Function.
I've placed all in the 'Custom Control'-Folder of FF.

Now, when I try to create the control (the tool for it is ok), I get an Error-Message: 'Error creating the custom control. Parameters: ......'

If I make a direct call to the 'CreateWindowEx' in the .ctl-File it works.

What do I wrong?

Christian

Paul Squires

Quote
If I make a direct call to the 'CreateWindowEx' in the .ctl-File it works.
I assume that the function that creates your textbox is called "WSTEXTBOX" in your include file. In that function you are calling CreateWindowEx, right? That function is "exported", right?

By looking at the ctl file I can not see anything there that looks wrong. Maybe you can email me the project so I can trace it through as it trys to create an instance of the control in the FF3 designer?
Paul Squires
PlanetSquires Software

Christian Weilguny

Hi Paul,

of course, in a few minutes you will have the project with the dll, the include-file and the .ctl-File.

Thanks
Christian

Paul Squires

Hi Christian,

I got your email. I'll send you back the modified files shortly.

For those reading this post, there were a few problems with the .ctl file:

(1) The ALIAS in the create_action property was uppercase but Christian specifically used a non-uppercase Alias when he defined the exported function from the DLL.

(2) No need for use_loadlibrary = 1. Changed that to 0.

(3) The ctl was missing properties for Top, Left, Width, Height (and I added Font as well because his textbox would benefit from it).

(4) He defined notification messages but the WsTextBox.inc did not have any of the equates defined. eg. %WSTEXTBOX_MSG_CHANGE    = %WM_USER + 1  so I added those.

Paul Squires
PlanetSquires Software

Christian Weilguny

#4
Hi Paul,

thanks very much.

I have not made properties for the font and the colors, because this values come from the registry (that,s why I do this). The top, left, width and height must be there, that's ok.

At one try I killed all message-definitions from the .ctl-File, but it doesn't work (because there where other bugs, too -:)

I didn't know, that aliase in declarations are case-sensitive. My SDK-programming level is to low -:)

In which case I need the use_loadlibrary ?
May be there is a way to put the dll-File for more then one custom-controls at a central place? Or have the dll to be in the same folder with the .ctl?

Christian

Christian Weilguny

Hi,

in the meantime I have figured out, the "FF_Control_SetColor" - Function does not work with my custom control.
There is no reaction at all.

I have tried it in the dll and in the mainprog, but there are the syscolors every time.
I have put the "Background Color" - property in the .ctl.
When I change the property in the designer I see the changed color (e.g. yellow). When I run the program, it is white.

What may be wrong?

Christian

Paul Squires

#6
Quote from: Christian Weilguny on September 07, 2010, 08:33:10 PM
...the "FF_Control_SetColor" - Function does not work with my custom control.
There is no reaction at all.

This could be a problem for you. The way that I coded FF3 is to disable color processing for all external controls. That way FF3 does not interfere with any processing that a custom control would do. But, this means that the control itself will need to handle the coloring. this is what I do with FireTextBox, FireLink, etc...

As you can see from the generated code, the 6th parameter is FALSE (nProcessColor). Therefore, using FF_Control_SetColor will have no effect in the generated code because the settings will be ignored by FLY_SetControlData.

    ff = FLY_SetControlData( hWndControl, %TRUE, %TRUE, _
                 "Tahoma,-13,0,0,0,400,0,0,0,0,3,2,1,34", 0, %FALSE, _
                 %FALSE, %FALSE, %FALSE, _
                 %FALSE, -1, CodePtr(FORM1_CODEPROCEDURE), "" )

The theory is that the custom control should handle color itself. In your case, the best approach is to have a custom message to set the back color and handle it from within the control. You would need to have a parent window in your DLL and use that as the parent window for your TextBox. You would need to save the color/brush using a Prop (GetProp/SetProp) so each instance of the control could have it's own color. In the Form's CUSTOM message handler you would handle the WM_CTLCOLOREDIT message to return a brush for the background color. This is not trivial stuff especially if you don't have a lot of api experience. Sorry.




Paul Squires
PlanetSquires Software

Paul Squires

Quote from: Christian Weilguny on September 07, 2010, 12:46:32 PM
In which case I need the use_loadlibrary ?
Some controls need to use LoadLibrary api call to create an instance of the DLL at runtime. This is somewhat rare these days especially in the PB world so i wouldn't worry about it.

Quote
May be there is a way to put the dll-File for more then one custom-controls at a central place? Or have the dll to be in the same folder with the .ctl?
The dll needs to be in the same folder as the ctl. That's where FF3 expects to find the dll.
Paul Squires
PlanetSquires Software

Christian Weilguny

Hi Paul,

it's a pity that the colors do not work.
I want only one time (at creation) to set the color from a value in the registry.
And you're right, it's the best to do this in the control-dll, I've first tried to do so. But till now I've no 'generic' way found to do it.
I will have an intensive look at your 'FireTextBox', perhaps I see, what I need.

What meaning has the 'use_initialize_action' in the .ctl-file? I thought, this can be a solution for me.

Christian

Paul Squires

Hi Christian,

Hopefully I will have some time today where I can try to create a little demo for you to see if we can make this work. I will be in touch.
Paul Squires
PlanetSquires Software

Paul Squires

Hi Christian,

Here is my first attempt at implementing the type of control that you need. Hopefully it will work okay for you.


Function WsTextBox Alias "WsTextBox"( ByVal hWndFrm  As Long, _
                                      ByVal hID      As Long, _
                                      ByVal lLeft    As Long, _
                                      ByVal lTop     As Long, _
                                      ByVal lWidth   As Long, _
                                      ByVal lHeight  As Long, _
                                      ByVal lStyle   As Long, _
                                      ByVal lExStyle As Long _
                                      ) Export As Dword     
     
    Dim hWndParent  As Dword
    Dim hWndControl As Dword
    Dim IsVisible   As Long
    Dim hInst       As Long
    Dim wc          As WNDCLASSEX
    Dim szClassName As Asciiz * 20
   
    hInst  = GetWindowLong(hWndFrm, %GWL_HINSTANCE)
    lStyle = lStyle Or %WS_CHILD Or %WS_VISIBLE
     
    ' Create a parent window that will hold the textbox control. We use that window to
    ' handle the color notification messages and to reflect any messages sent to it
    ' down to the child textBox control.
   
    szClassName         = "WSTEXTBOX"
     
    'if not already registered
    If GetClassInfoEx(GetModuleHandle(ByVal %Null), szClassName, wc) = 0 Then
       wc.cbSize        = SizeOf(wc)
       wc.Style         = %CS_HREDRAW Or %CS_VREDRAW
       wc.lpfnWndProc   = CodePtr(WsTextBoxProc)
       wc.cbClsExtra    = 0
       wc.cbWndExtra    = 4
       wc.hInstance     = hInst
       wc.hIcon         = %Null
       wc.hCursor       = %Null
       wc.hbrBackground = %Null
       wc.lpszMenuName  = %Null
       wc.lpszClassName = VarPtr(szClassName)
   
       If RegisterClassEx(wc) = 0 Then
          Exit Function
       End If   
    End If
     
    hWndParent = CreateWindowEx( 0, szClassName, "", lStyle, _
                                 lLeft, lTop, lWidth, lHeight, _
                                 hWndFrm, hId, hInst, ByVal 0)
     
    Function = hWndParent
       
    hWndControl = CreateWindowEx( lExStyle, "Edit", "", _
                                  lStyle, 0, 0, lWidth, lHeight, _
                                  hWndParent, 100, hInst, ByVal %Null )
                                   
    ' Set the default font for the control.
    SendMessage hWndControl, %WM_SETFONT, GetStockObject(%DEFAULT_GUI_FONT), %TRUE 
         
         
    ' Save the background color and background brush values to properties
    ' of the parent window.
    SetProp hWndParent, "BACKCOLOR", %Yellow
    SetProp hWndParent, "BACKBRUSH", CreateSolidBrush(%Yellow)
           
           
    Function = hWndParent
             
End Function
             
               
'------------------------------------------------------------------------------
Function WsTextBoxProc( ByVal hWnd   As Dword, _
                        ByVal wMsg   As Long, _
                        ByVal wParam As Long, _
                        ByVal lParam As Long _
                        ) As Long

    Local hWndEdit As Dword
     
    If wMsg <> %WM_CREATE Then
       hWndEdit = GetDlgItem(hWnd, 100)
    End If
       
       
    Select Case wMsg
     
       Case %WM_SIZE
          ' Make sure that the child TextBox resizes to fill the entire
          ' width/height of the parent window.
          SetWindowPos hWndEdit, %HWND_TOP, 0, 0, LoWrd(lParam), HiWrd(lParam), 0
     
       
       Case %WM_SETFOCUS
          'set the focus to the child edit control
          SetFocus hWndEdit           
          Function = 0: Exit Function
       
         
       Case %WM_NOTIFY  'forward to parent 
          SendMessage GetParent(hWnd), wMsg, wParam, lParam
           
           
       ' Forward all edit control messages to the embedded edit control
       Case %WM_SETTEXT, %EM_GETSEL To %EM_GETIMESTATUS
          If IsWindow(hWndEdit) Then
             SendMessage hWndEdit, wMsg, wParam, lParam
          End If
             
             
       Case %WM_GETTEXTLENGTH
          ' Get the text length from the edit control.
          If IsWindow(hWndEdit) Then 
             Function = SendMessage( hWndEdit, %WM_GETTEXTLENGTH, wParam, lParam)
             Exit Function
          End If                         
             
               
       Case %WM_GETTEXT
          ' Get the text from the edit control.
          If IsWindow(hWndEdit) Then 
             Function = SendMessage( hWndEdit, %WM_GETTEXT, wParam, lParam)
             Exit Function
          End If                         
               
                 
       Case %WM_CTLCOLOREDIT
          ' return the background brush
          Dim hBackColor As Long
          Dim hBackBrush As Dword
          hBackColor = GetProp(hWnd, "BACKCOLOR")
          hBackBrush = GetProp(hWnd, "BACKBRUSH")
          SetBkColor wParam, hBackColor
          Function = hBackBrush               
          Exit Function
                 
                   
       Case %WM_ENABLE
          'If the state of the window is changing then we must repaint the
          'edit control in order for the text color to be correctly shown.
          EnableWindow hWndEdit, wParam
          InvalidateRect hWnd, ByVal %Null, %TRUE: UpdateWindow hWnd
          Function = 0: Exit Function
                   
                     
       Case %WM_DESTROY
          ' make sure that we destroy the back brush handle before
          ' the control is destroyed in order to avoid a GDI leak.
          DeleteObject GetProp(hWnd, "BACKBRUSH")
          RemoveProp hWnd, "BACKCOLOR"
          RemoveProp hWnd, "BACKBRUSH"
                     
    End Select
                       
    Function = DefWindowProc(hWnd, wMsg, wParam, lParam)
                       
End Function
                                 

Paul Squires
PlanetSquires Software

Christian Weilguny

Hi Paul,

Thanks very much, I had a similar solution elaborated (with some bugs :)

It's possible to set the colors directly like this?

       Case %WM_CTLCOLOREDIT
          ' return the background brush
          SetBkColor wParam, FF_GetRegistryDword(...)
          Function = FF_GetRegistryDword(...)             
          Exit Function


Christian

Paul Squires

Quote from: Christian Weilguny on September 09, 2010, 04:46:51 PM
It's possible to set the colors directly like this?

Almost... you need to return a brush handle from WM_CTLCOLOREDIT, not the color value itself. You will notice in my example that I retrieve the brush that I created when the control was created. I create the brush at that time rather than having to delete an existing brush, retrieve the color value, create the new brush and return it for every WM_CTLCOLOREDIT message. You need to handle that brush carefully otherwise you will end up with GDI resource leaks.


         
Paul Squires
PlanetSquires Software

Christian Weilguny

Hi Paul,

now it works!
But I had to set the width and height for the hWndEdit initially to the correct values (in CreateWindowEx), because otherwise the control was not visible (only the container-window).

Quote... you need to return a brush handle from WM_CTLCOLOREDIT

This is my coloredit-message:

         Case %WM_CTLCOLOREDIT
            'wParam hold the hDC
            SetTextColor wParam, FF_GetRegistryDWord( %HKEY_CURRENT_USER, $USR_REG_PATH, "TxtForeColor", %RGB_BLACK)
            SetBkColor wParam, FF_GetRegistryDWord( %HKEY_CURRENT_USER, $USR_REG_PATH, "TxtBkColor", %RGB_LIGHTCYAN)
            Function = CreateSolidBrush(FF_GetRegistryDWord( %HKEY_CURRENT_USER, $USR_REG_PATH, "TxtBkColor", %RGB_LIGHTCYAN))
            Exit Function


In this case the brush is only return value. I think not, I have to destroy it. I hope, I'm right?

Christian

Paul Squires

You can not do this:

Function = CreateSolidBrush(FF_GetRegistryDWord( %HKEY_CURRENT_USER, $USR_REG_PATH, "TxtBkColor", %RGB_LIGHTCYAN))


Every time the message is processed you will leak a GDI resource because you are creating a new brush on EVERY call. That's why you need to create the brush once when the control is created and then use that brush over and over and finally destroy it in WM_DESTROY. Check out the sample code that I posted earlier - it does exactly that.
Paul Squires
PlanetSquires Software