The first step in improving the product was to reduce the amount of coding required. Instead of repeating each individual pixel code, I needed to load the base pixels into memory and use loops to paint them. The other alternative was to load the necessary bitmaps and use BitBlt. But that still meant looping through the bitmaps to change the color, so I opted for the first alternative.
In order to accomplish this, I standardized the corners using a 16 x 11 Long array and removed the near white border that the original code created. I also modified the code to use Long variables instead of Doubles or Singles, as Longs are more efficient. There are however a few exceptions that required greater accuracy.
I also found a better algorithm to apply different Hues to the button. The information and a good explanation of color application was found here: http://www.tannerhelland.com/3643/gr...algorithm-vb6/
in a link called "How to Colorize an Image (in VB6)". Although these pages provided a decent explanation of the principles of color and the code to change the color, it did not provide an explanation of the principles of the math used. If anyone can direct me to such an explanation, it would be appreciated.
The individual pixel colors were loaded into a 15 x 45 array using an included program called Colors.vbp, and saved into a file called Color.bin. The first time Button.exe creates a button, the file is loaded and read into memory as a Long array. The different Hues are applied to the vbBlue data to allow the buttons to change color. VbWhite is used to create the Gray color, and does this by changing the Saturation value (S1) to zero. I currently use vbGreen as the default color, vbCyan as the Mouse Over color, and vbMagenta as the Got Focus color. Got Focus has precedence over Mouse Over. Any valid Hue can be used, but the results are unpredictable and may require further error checking. Sticking to the standard VB colors is your best bet (vbWhite, vbBlue, vbGreen, vbRed, vbYellow, vbCyan, vbMagenta).
There were several interesting quirks that I ran into. If the array element is zero, the present code ignores it. But if you remove that check, the pixel will be displayed black. Also notable was that pixels outside the area reserved for the button would not display. The window background would take precedence. You can verify this by commenting out the rounded corner code in "DrawButton" and removing the check for zero code in "DrawMACButton".
The "DrawMacOSXColored" routine was used to develop "Colors.vbp". It is inefficient and not needed, but I left it intact in case you want to play with the base coloring (Blue). It is much easier to do it there and transfer the results to the data file later. This project was very time consuming, and reminded me once again why I don't particularly care to mess with graphics.
What would be nice to add to this button would be a "Font" property, but I will leave that for another day.
J.A. Coutts
MACButton.zip updated 11/28/2019
Last edited by couttsj; Nov 28th, 2019 at 06:57 PM.
The Font property has bee added. It took me a while to get this working. The first problem was that every time I went to change the Font property, I would get an error message that it wasn't supported. It wasn't until I realized that it uses "Property Set" instead of "Property Let".
Code:
Private WithEvents Nfont As StdFont
Public Property Get Font() As StdFont
Set Font = Nfont
End Property
Public Property Set Font(ByVal mnewFont As StdFont)
With Nfont
.Bold = mnewFont.Bold
.Italic = mnewFont.Italic
.Name = mnewFont.Name
.Size = mnewFont.Size
End With
PropertyChanged "Font"
End Property
Private Sub Nfont_FontChanged(ByVal PropertyName As String)
Set UserControl.Font = Nfont
Refresh
End Sub
Private Sub UserControl_Initialize()
Set Nfont = New StdFont
End Sub
The next problem was that it would not remember any changes I made to the Font property. I tried many different things and finally got it to work by accident. What I failed to realize was that "StdFont" is specific to VB6/VBA.
Code:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Set Font = PropBag.ReadProperty("Font", Ambient.Font)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Font", Nfont)
End Sub
I also added 3 color properties (ColorNormal, ColorHot, & ColorFocus). These are entered and saved as Long values for simplicity, so the easiest way to enter them is as Hex values.
vbWhite = &H0
vbRed = &HFF
vbGreen = &HFF00
vbBlue = &HFF0000
vbYellow &HFFFF
vbMagenta = &HFF00FF
vbCyan = &HFFFF00
In your Set Font method, why do you transfer the properties instead of simply: Set nFont = mnewFont ?
Tip: When a property is an object, you can allow both Let & Set so the user can assign the property with/without using the Set keyword. It goes a little like this:
Code:
Public Property Let Font(mnewFont As StdFont)
Set Me.Font = mnewFont
End Property
Public Property Set Font(mnewFont As StdFont)
If Not mnewFont Is Nothing Then
Set UserControl.Font = mnewFont
Set tFont = mnewFont ' if this doesn't trigger your font change event, redraw button
PropertyChanged "Font"
End If
End Property
Public Property Get Font() As StdFont
Set Font = tFont
End Property
FYI. Typically a control is assigned the container's Font when the control is first created, not some default font set during Initialize.
Code:
Private Sub UserControl_InitProperties()
Set nFont = Ambient.Font
End Sub
Edited: You can offer your color properties a different way also, allowing user to choose from a list. Sample shown just FYI
Code:
Public Property Get ColorNormal() As ColorConstants
... return the color
End Property
Public Property Let ColorNormal(ByVal newColor As ColorConstants)
... set color to variable & draw button
End Property
Just food for thought. I am not a big ally for calling property methods when reading propertybag items when those actions result in drawing actions. Why? Typically, several different properties result in drawing when those properties change, each triggering a call to the controls "Draw" method. It is better, IMO, to set the variables from the propertybag and then after all are set, call the "Draw" method.
Code:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Set NFont = PropBag.ReadProperty("Font", Ambient.Font)
... set your color variables
... read the rest of your properties
... and when done with that, then call your "draw" function; Refresh in this case
End Sub
Private Sub UserControl_InitProperties()
Set NFont = Ambient.Font
... initialize your color variables & any other UC variables
... and when done with that, then call your "draw" function; Refresh in this case
End Sub
Last edited by LaVolpe; Nov 24th, 2019 at 04:18 PM.
Insomnia is just a byproduct of, "It can't be done"
According to Microsoft: "Notice that this code uses With to set each property of the StdFont object individually: Simply assigning mnewFont to mFont would only change the default Name property."
When I tried what you suggested, it failed to recover the saved Font properties.
When I tried what you suggested, it failed to recover the saved Font properties.
J.A. Coutts
I think that article is misleading. Notice there isn't any sample code regarding saving/retrieving saved font. I have never had any issues if done in the "proper" way. I think that article was trying to suggest that simply changing the font or a font property doesn't trigger painting... Ok, but we can handle it just fine
- Call Refresh (or paint routine) before exiting Set Font
Note: MSDN article was changing font properties in Set Font which triggers the FontChanged event. In their example, 4 properties set, up to 4 redraws of the control. I'd prefer no properties individually set, 1 redraw.
Note: Their example is flawed any way. How? Notice they are checking 4 properties. If none of those properties change, then no FontChanged event occurs. If all 4 changed, then 4 FontChanged events occur. What happens if your only change was to make it underlined? No FontChanged event because they didn't include that check, nor for strikethrough. Even worse, those properties are not preserved in the updated font. Just a bad example overall IMO.
- Call Refresh (or paint routine) when FontChanged event triggers.
Note: FontChanged only triggers during runtime when user does something like UserControlXYZ.Font.Bold = True. It doesn't trigger just by setting the Font property, which is why you want to call Refresh before exiting that property.
1. Whenever assigning a new font, two steps needed. If you Debug.Print NFont or UserControl.Font properties, they will correctly show Bold, Italic, Size, etc
Code:
Dim WithEvents NFont As StdFont
Public Property Set Font(newFont As StdFont)
Set NFont = newFont
Set UserControl.Font = NFont
PropertyChanged "Font"
... refresh/repaint as needed; this removes requirement to force FontChanged events
End Property
2. When Saving the Font...
Code:
Private Sub UserControl_WriteProperties(Propbag As PropertyBag)
PropBag.WriteProperty "Font", NFont ' or usercontrol.font; doesn't matter
End Sub
3. When Retrieving font, two steps needed. If you Debug.Print NFont or UserControl.Font properties, they will correctly show Bold, Italic, Size, etc
Code:
Private Sub UserControl.ReadProperties(PropBag As PropertyBag)
Set NFont = PropBag.ReadProperty("Font", Ambient.Font)
Set UserControl.Font = NFont
End Sub
4. Starting brand new control
Code:
Private Sub UserControl_InitProperties()
Set NFont = Ambient.Font
Set UserControl.Font = NFont
End Sub
5. Only action needed in FontChanged is to redraw
Code:
Private Sub Nfont_FontChanged(ByVal PropertyName As String)
Refresh ' or call your paint routine
End Sub
6. Notice I am not setting any font during Initialize
Using the above outline and Debug.Print font properties during UserControl.Paint for verification, I think you'll find no problems at all. It doesn't matter whether the font is set at design time via property page or at run time
Last edited by LaVolpe; Nov 25th, 2019 at 05:28 PM.
Insomnia is just a byproduct of, "It can't be done"
Thank you for the detailed explanation. Using a Debug.Print statement, my original code shows 3 trips to the FontChanged routine complete with the associated refresh (Why there is only 3, I have no idea). Implementing all of your code works just fine and shows no trips to the FontChanged routine, which suggests that it is not needed at all.
Am I wrong in coming to this conclusion? Also, the Refresh in the Property Set Font routine was indeed necessary.
Implementing all of your code works just fine and shows no trips to the FontChanged routine, which suggests that it is not needed at all.
Am I wrong in coming to this conclusion? Also, the Refresh in the Property Set Font routine was indeed necessary.
Setting the individual font properties is not needed if you Refresh before exiting Set Font property. The FontChanged event is still desirable should the user simply change a single font attribute @ runtime, i.e.
Code:
UserControl1.Font.Bold = Not UserControl1.Font.Bold ' toggling value would trigger FontChanged
P.S. The reason you didn't have 4 trips was because the event should only fire when an attribute changes value. For example, if the Bold attribute remained the same, it shouldn't trigger a change event after .Bold was set
Last edited by LaVolpe; Nov 26th, 2019 at 07:12 AM.
Insomnia is just a byproduct of, "It can't be done"
This latest version includes the changes to the Font property suggested by LaVolpe, as well as moving the pixel colors to a Resource file. Using the Resource file eliminates the necessity of coupling the User Control to the Colors.bin file, and presumably is a little faster (not verified).
And finally an ActiveX control. There are advantages and disadvantages to using such a control. The advantage is that it reduces the size of your new project, but the disadvantage is that you must distribute and register the OCX.
After compiling, it should be moved to a common directory such as \Windows\System32\ (\Windows\SysWOW64\ for 64 bit), and then added to your Project Components.
There is also a short Test program included. The TypeLib key for your Button.OCX will be different than the one included in the test sample.
And finally an ActiveX control. There are advantages and disadvantages to using such a control. The advantage is that it reduces the size of your new project, but the disadvantage is that you must distribute and register the OCX.
After compiling, it should be moved to a common directory such as \Windows\System32\ (\Windows\SysWOW64\ for 64 bit), and then added to your Project Components.
There is also a short Test program included. The TypeLib key for your Button.OCX will be different than the one included in the test sample.
J.A. Coutts
use CreateRoundRectRgn,The 4 corners of the control have been deducted, which is similar to a transparent effect, except that there are raw edges and white edges.
use my code ,Copy the picture from the parent object of the control as the background image of the custom control, so that 100% transparency can be achieved
The Max button looks great. I did change the 3 colors from long to Ole_Color and added CaptionForeColor property. I haven't been able to make it multi line caption yet. Thats the only big improvement I'd like to see made.