PHP User Warning: fetch_template() calls should be replaced by the vB_Template class. Template name: bbcode_highlight in ..../includes/functions.php on line 4197

PHP User Warning: fetch_template() calls should be replaced by the vB_Template class. Template name: bbcode_highlight in ..../includes/functions.php on line 4197
Creating a new Command Button-VBForums
Results 1 to 18 of 18

Thread: Creating a new Command Button

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Creating a new Command Button

    The MAC OSX button in the Glossy Buttons post by Derp! interested me, but I had never used the User Control in that manner before, so it was a learning experience. In this edition, I will outline the basic steps in creating a User Control command button.

    On a new project, create a User Control, resize it to your liking, and change the back color to pure white (&H00FFFFFF&). A new icon will appear in the Toolbox. When you add the control to a form, the Initialize, InitProperties, Resize, Show, & Paint events will fire. When you close the form and reactivate it, all but the InitProperties will fire. So InitProperties is a good place to load initial properties. Then we can save those properties using WriteProperties when the form is closed, and recover them the next time the form is displayed using ReadProperties. It is interesting to note that displaying the form in the IDE fires the same events as running the program in the IDE.

    But first we have to resize the new form control and the results are automatically saved. To test the size adjustments, close the form and redisplay it. The new control (UserControl1) will be displayed as a white rectangle.

    Any good control needs to be identified, so lets add a Caption. Display the code page for the new control, and select "Add Procedure" from the Tools menu. Enter the name as "Caption", select "Property", and click "OK". We now have "Get Caption" and "Let Caption" functions added to UserControl1. On the Get function, change Variant to String and add code "Caption = m_Caption". "m_Caption" doesn't exist yet, but we will add it in a bit. On the Let function, change "vNewValue as Variant" to "NewCaption as String" and add the following code:
    m_Caption = NewCaption
    PropertyChanged "Caption"
    UserControl_Resize
    Call ucRefresh
    "ucRefresh" doesn't exist yet, so comment it out for now. Before we can test our handiwork, we have to define "m_Caption". Add "Private m_Caption As String" to the Declarations section. Display the form and notice that the new control now has a Caption property. We still cannot add a caption name because we need a little more code. This is where InitProperties comes into play. Add "m_Caption = UserControl.Extender.Name" to UserControl_InitProperties. This adds the current name of the control (UserControl1). Because this event only fires when you add a control to the form, we need to add a little more code to remember it.
    Code:
    Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
        m_Caption = PropBag.ReadProperty("Caption", m_Caption)
    End Sub
    
    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
        Call PropBag.WriteProperty("Caption", m_Caption, "")
    End Sub
    Now delete the new control from the form and add it back in. Now when you select it, "UserControl1" will be shown as the Caption name. Change it to whatever you want, such as "MyControl". Now when you close the form and open it again, the Caption property will show "MyControl".

    But that still doesn't display the caption on the control itself. To do that we have to actually draw the text on the control, and this takes more code. To accomplish this task, we need a few more Declarations.
    Code:
    Option Explicit
    
    Private Const DT_CALCRECT = &H400
    Private Const EDGE_RAISED As Long = &H5
    
    '*************************************************************
    '   Private Constants
    '**************************************
    Private m_Caption As String
    
    '*************************************************************
    '   Type Definitions
    '*************************************************************
    Private Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
    End Type
    
    '*************************************************************
    '   API Declarations
    '*************************************************************
    Private Declare Function DrawText Lib "user32" Alias "DrawTextA" (ByVal hdc As Long, ByVal lpStr As String, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long
    Private Declare Function SetRect Lib "user32" (lpRect As RECT, ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long
    Private Declare Function OffsetRect Lib "user32.dll" (ByRef lpRect As RECT, ByVal x As Long, ByVal y As Long) As Long
    Private Declare Function DrawEdge Lib "user32" (ByVal hdc As Long, qrc As RECT, ByVal edge As Long, ByVal grfFlags As Long) As Long
    and a routine to draw the Caption.
    Code:
    Private Sub DrawButton()
        Dim sCaption As String
        Dim btnRect As RECT 'Button rectangle
        Dim txtRect As RECT 'Caption rectangle
        'Get dimensions of control in pixels.
        btnRect.Right = ScaleX(UserControl.ScaleWidth, UserControl.ScaleMode, vbPixels)
        btnRect.Bottom = ScaleY(UserControl.ScaleHeight, UserControl.ScaleMode, vbPixels)
        UserControl.BackColor = vbGreen 'Give it some color
        'Calculate caption rectangle & adjust for border
        SetRect txtRect, 0, 0, btnRect.Right - 8, btnRect.Bottom - 8
        sCaption = m_Caption
        'Modify rect to exact size
        DrawText UserControl.hdc, m_Caption, -1, txtRect, DT_CALCRECT
        'Center Caption
        OffsetRect txtRect, (btnRect.Right - txtRect.Right) \ 2, (btnRect.Bottom - txtRect.Bottom) \ 2
        'Draw the Caption
        DrawText UserControl.hdc, sCaption, -1, txtRect, 0& 'dtFlags
        'Draw button border
        DrawEdge UserControl.hdc, btnRect, EDGE_RAISED, &HF
    End Sub
    Now call this routine from the UserControl_Show event (Call DrawButton). Run the program or display the form. What? No Caption displayed! We need to add one small command that allows the system to process messages.
    Code:
    Private Sub UserControl_Show()
        DoEvents
        Call DrawButton
    End Sub
    Now we should see the Caption displayed in the center of the new button with a green background and a lower right shadow.

    In the next installment we will cause the background color to change whenever the mouse hovers over the button, or when the button has the focus.

    J.A. Coutts
    Attached Images Attached Images  
    Last edited by couttsj; Sep 25th, 2019 at 06:24 PM.

  2. #2
    Lively Member
    Join Date
    Jun 2016
    Posts
    86

    Re: Creating a new Command Button

    very interesting.
    still she looks good so this guide.

    Greetings

  3. #3

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Installment 2

    In this installment, we will cause the button color to change when the mouse is moved over the control and when the focus is shifted to the control.

    To detect when the mouse is moved over the new control is relatively straight forward. UserControl_MouseMove is only activated when the mouse enters the control. Detecting the mouse leaving the control is somewhat more difficult. The TrackMouseEvent API call will deliver that response once it has been charged with the responsibility, and that delivery is via a "WM_MOUSELEAVE" Windows message. We therefore must implement an IDE safe Windows messaging system, for which Paul Caton's Self Subclassed template is as good as any. I leave it up to the user to follow that implementation.

    There is too much involved with this update to go through it step by step, so I have attached a zip file. We have added a normal Command button and set its TabIndex to "0" so that it will take the initial focus. When run, depressing the Tab key will shift the focus to the new control and change the back color to red. Depressing it again will shift the focus back to Command1, and change the back color back to normal green. Moving the mouse over the new control will change its back color to magenta. This takes place in the UserControl_MouseMove subroutine, and I have added public events MouseEnter & MouseLeave. When the mouse leaves the control, the system sends a "WM_MOUSELEAVE" message and terminates the mouse tracking. The "WM_MOUSELEAVE" message is intercepted by the zSubclass_Proc subroutine and restores the normal green color as well as raising a MouseLeave event. The Focus color change takes precedence over the MouseEnter color change.

    On the next installment, we will change the control shading and shape to that of a MAC OSX style shaded button.

    J.A. Coutts
    Attached Files Attached Files

  4. #4

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Installment 3

    In this installment, we will change the control shading and shape to that of a MAC OSX style contoured button.

    The first step is to remove the border and the coloring, and make the corners rounded. After creating the original rectangular button, the rounded corners was accomplished using the API call CreateRoundRectRgn.
    For the shading of the button, I borrowed 3 routines from Derp's post; DrawMacOSXButtonNormal, DrawMacOSXButtonHot, and DrawMacOSXButtonPressed.

    To give you an idea of how the shading is done, I captured 9 photos of the various regions. The first region is the caption rectangle, then the four corners, followed by the two end pieces and lastly the upper and lower blocks.

    In these routines, the coloring seems to be fixed, so I was wondering if there is any way of making the coloring configurable?

    J.A. Coutts
    Attached Images Attached Images  
    Attached Files Attached Files

  5. #5
    Lively Member
    Join Date
    Jun 2016
    Posts
    86

    Re: Creating a new Command Button

    very interesting.
    When you click on the button and then move the mouse outside still maintaining the active button, not leaving deveria disable the mouse
    the button.

    nice job

  6. #6

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Quote Originally Posted by yokesee View Post
    very interesting.
    When you click on the button and then move the mouse outside still maintaining the active button, not leaving deveria disable the mouse
    the button.

    nice job
    Not sure what you are saying, but I think you are saying that the button retains the dark blue color when a depressed mouse is moved off the button. When you click on a button, it takes the focus and takes on the coloration of DrawMacOSXButtonPressed. Unfortunately, that coloration is similar to DrawMacOSXButtonHot, and that is why I would like to find a way to use a single routine to provide different color schemes.

    J.A. Coutts

  7. #7
    Junior Member Derp!'s Avatar
    Join Date
    Jan 2019
    Posts
    21

    Re: Creating a new Command Button

    Quote Originally Posted by couttsj View Post
    Not sure what you are saying, but I think you are saying that the button retains the dark blue color when a depressed mouse is moved off the button. When you click on a button, it takes the focus and takes on the coloration of DrawMacOSXButtonPressed. Unfortunately, that coloration is similar to DrawMacOSXButtonHot, and that is why I would like to find a way to use a single routine to provide different color schemes.

    J.A. Coutts
    I've got the original button tintable (applying the hue of a selected color to the color of each pixel set in the DrawMacOSX sub). However, I didn't really like this so I ended up rewriting a custom button that looks like the MacOSX but written using GDI+ APIs. I also added some other button styles from another button control and a radio button style (in addition to the standard button and Checkbox styles). I've got it almost ready but I can post the current version to my post if you want to play with it. (See below). I also changed it so that all of the drawing is done in the memotry to a DC created when the picture is drawn and the final image Blted to the control when all was done.

    Name:  update.jpg
Views: 209
Size:  36.9 KB
    Last edited by Derp!; Sep 30th, 2019 at 10:13 PM.

  8. #8
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    1,555

    Re: Creating a new Command Button

    Code:
    Private Sub UserControl_Show()
        DoEvents
        Call DrawButton
    End Sub
    You are starting on the wrong foot w/ this DoEvents here (any everywhere else) IMO. . .

    cheers,
    </wqw>

  9. #9

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Quote Originally Posted by Derp! View Post
    I've got the original button tintable (applying the hue of a selected color to the color of each pixel set in the DrawMacOSX sub). However, I didn't really like this so I ended up rewriting a custom button that looks like the MacOSX but written using GDI+ APIs. I also added some other button styles from another button control and a radio button style (in addition to the standard button and Checkbox styles). I've got it almost ready but I can post the current version to my post if you want to play with it.
    Why not? You are the one that got me interested in this in the first place.

    J.A. Coutts

  10. #10

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Quote Originally Posted by wqweto View Post
    Code:
    Private Sub UserControl_Show()
        DoEvents
        Call DrawButton
    End Sub
    You are starting on the wrong foot w/ this DoEvents here (any everywhere else) IMO. . .

    cheers,
    </wqw>
    Used properly, I have never had a problem with DoEvents. I only use it in cases where I need to stop the current thread in order to allow system messages to be processed. Some people use it as a time delay, for which it was never intended.

    J.A. Coutts

  11. #11
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    1,555

    Re: Creating a new Command Button

    Quote Originally Posted by couttsj View Post
    I only use it in cases where I need to stop the current thread in order to allow system messages to be processed.
    The form your control is sited on can be unloaded at this time (the DoEvents call) so the following DrawButton call will suffer all sorts of nastiness, mostly a great number of built-in user-control properties/methods will raise "client site not available" error. I didn't following the code exactly but there is no need to call DoEvents to redraw a hWnd. Just call UpdateWindow and it will pump/send WM_PAINT msg as necessary.

    You are not stopping current thread w/ DoEvents because current thread can be pre-empted at any time by the scheduler (no need for explicit DoEvents) and besides DoEvents call is executed on the current thread and system messages processing is happening on the current thread, e.g. the parent form gets unload on current thread. There is little to none multi-threading in DoEvents call.

    Btw, the only way to pause a thread is w/ WaitForSingle/MultipleEvents API call (or terminating it) and you can yield current thread quantum w/ a simple Sleep API call but in a topic about a user-control, threading is not what this thread is about (I suppose).

    cheers,
    </wqw>

  12. #12

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Microsoft says this about DoEvents:
    "Consider what happens during a DoEvents call. Execution of application code is suspended while other forms and applications process events."
    "Caution Any time you temporarily yield the processor within an event procedure, make sure the procedure is not executed again from a different part of your code before the first call returns; this could cause unpredictable results. In addition, do not use DoEvents if other applications could possibly interact with your procedure in unforeseen ways during the time you have yielded control."

    They then go on to explain situations where it may produce unexpected results, and most of those involve loops or functions that may get re-entered. There is no such danger in the situation I have used it, and if you remove the DoEvents, none of the button graphics will appear in the initial form presentation. Mouse over the button and it is back to normal. I am assuming that this related to the delayed graphics presentation while the form itself loads (implemented a few VB versions back to speed up loading). It is quite conceivable that there is an API call that will do the same thing, but why would I go to that much trouble when DoEvents will do the job quite adequately when used properly.

    J.A. Coutts

  13. #13
    Frenzied Member
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    1,360

    Re: Creating a new Command Button

    If you call DrawButton in the UserControl_Paint event, you need neither DoEvents nor UpdateWindow.

  14. #14

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Quote Originally Posted by jpbro View Post
    If you call DrawButton in the UserControl_Paint event, you need neither DoEvents nor UpdateWindow.
    Thanks jpbro. Your timing is unbelievable. I had already tried that and was about to post it, when I discovered your message. From this, can I conclude that my suspicions about delayed graphics is correct?

    J.A. Coutts

  15. #15
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    1,555

    Re: Creating a new Command Button

    > "Execution of application code is suspended while other forms and applications process events."

    This is not in the sense the current thread is paused. When in function A you call function B, the execution of function A is "suspended" until function B completes. There is nothing more about DoEvents except that while processing system messages your original function A can be re-entered and/or your user control's parent form might get unloaded.

    Consider a simple RaiseEvent Click statement in your button control. In the parent form in the buttons Click event handler the user of your control might put Unload Me statement which unloads parent form so that when your RaiseEvent Click statement returns, your control is no longer able to access parent client-site and some of its properties and methods (on UserControl base-class) start to fail with "client site not available" error.

    This is the same king of problem you have with DoEvents because in the pending mesage queue might have keyboard strokes waiting to be processed by current thread message queue (which DoEvents loops). Might be some shortcut like Alt+F4 which will close parent form and. . . you know the drill.

    Few people realize that every RaiseEvent in a user-control is as dangerous as DoEvents call.

    cheers,
    </wqw>

  16. #16

    Thread Starter
    Fanatic Member
    Join Date
    Dec 2012
    Posts
    772

    Re: Creating a new Command Button

    Quote Originally Posted by wqweto View Post
    >
    Few people realize that every RaiseEvent in a user-control is as dangerous as DoEvents call.

    cheers,
    </wqw>
    Interesting. Does this RaiseEvent issue exist in classes too? I don't use DoEvents very much, but I do use RaiseEvent quite a bit in classes, and I have yet to encounter a "client site not available" error.

    J.A. Coutts

  17. #17
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    1,555

    Re: Creating a new Command Button

    Here is a simple user-control

    thinBasic Code:
    1. Option Explicit
    2.  
    3. Event Click()
    4.  
    5. Private Sub UserControl_Click()
    6.     RaiseEvent Click
    7.     Debug.Print ContainerHwnd
    8. End Sub
    Here is a form with its Click event implemented

    thinBasic Code:
    1. Option Explicit
    2.  
    3. Private Sub Form_Click()
    4.     With New Form1
    5.         .Show
    6.     End With
    7. End Sub
    8.  
    9. Private Sub UserControl11_Click()
    10.     Unload Me
    11. End Sub
    You'll get "client site not available" on the Debug.Print ContainerHwnd line because upon return from RaiseEvent Click the form is already unloaded.

    cheers,
    </wqw>

  18. #18
    Junior Member Derp!'s Avatar
    Join Date
    Jan 2019
    Posts
    21

    Re: Creating a new Command Button

    Quote Originally Posted by couttsj View Post
    Why not? You are the one that got me interested in this in the first place.

    J.A. Coutts
    I posted the latest (possibly last) update in my original post. It now supports Ownerdrawn and has (probably) most bugs worked out.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width