Teach me the Magic
Some assumptions first. So that there will no misunderstanding of how I work.
You might need to adjust the code if you do things differently.
· When starting my applications I alway create a public application object that handles most of the behavior of the application.
That is also the place where I put the name of the skin I want to see. This makes it very easy to simply drop one object on the form where I want the skin to appear. So my application class has one property named “skin”.
· The value of this property is also the name of a folder under the application folder where all pictures reside. As well a a skin.ini file. More on that later.
· Besides the property “skin” my application object also has a method SetSkin.
This method reads my application ini file, locates the SKIN section and reads the SKIN key.
Using an ini file makes it easy for you to change the behavior of an application
without having to compile the project again.
I use Simon Arnold’s initoobject class for this, in the original the classname in the
prg is ClsinitoObject, I seem to have sticky fingers so I often made the typo
clsInitobject (one o), therefore I changed the classname to clsini2Object.
(look at download ID 106 on the download page of foxite.
In the SetSkin method of the application object, the ini file is read and if the Skin
section and the skin key are there the value of skin is put in the skin property of
the application object. Here is the code:
LOCAL loini as clsini2object OF ini2obj.prg
loini = NEWOBJECT("clsini2object","ini2obj.prg","",this.cIniFile)
lcSkin = loini.readvalue("Skin", "Skin")
IF NOT ISNULL( lcSkin)
this.skin = lcSkin
ENDIF
For the remainder of this article I will assume that you drop the formskinning object on the form itself.
I adjusted the code a bit so you can use it by dropping the object on your form.
So here’s the piece of code you need in the init of the form:
IF PEMSTATUS( goapp, "skin", 5) ;
AND NOT EMPTY( goapp.skin) ;
AND DIRECTORY( goapp.skin)
FOR EACH loControl IN this.Controls
IF UPPER(loControl.class) == "FORMSKINNING"
loControl.FormInit()
exit
ENDIF
ENDFOR
ENDIF
First the existence of the skin property is checked, if that is not empty AND there is a folder with the name of that skin only then the remainder of the code will run.
In the for … endfor loop the controls on the form are checked, if one of them is of the formskinning class the forminit method of that class will run.
Here’s the forminit:
LOCAL lcColor AS STRING
LOCAL loini as clsini2object OF initoobj.prg
loini = NEWOBJECT("clsini2object","initoobj.prg","", goapp.cInifile)
THIS.DECLARE()
WITH THISFORM
.ADDPROPERTY("ProxyBorderStyle",3)
.ADDPROPERTY("StartX",0)
.ADDPROPERTY("StartY",0)
ENDWITH
WITH this
.AddImages()
.ActivateImages()
.SetMousePointer()
.PositionImages()
.AddButtons()
.PositionButtons()
.AdjustProxyCaption()
ENDWITH
BINDEVENT( THISFORM, "resize", THIS, "cutframe")
BINDEVENT( thisform, "resize", THIS, "PositionButtons")
THISFORM.RESIZE()
· First the declare method is called, here the API functions needed for the skinning process are called.
· It adds three properties to the form
· then it adds the pictures, activates the images, this means that there is a bindevent for each of the images 1 to 6;
· It sets the mousepointers for each of the images 4 to 6,
· In the next step the close, max and min button are added.
· The buttons are positioned.
Mind you, since the position of the “buttons” (just shapes actually) are determined on the position of the righttop image (img3) the images need to be positioned first.
If you forget about this order things turn out to be so messy, and for sure, the fox doesn’t like messy workers.
· The next step is to adjust the captioncontrol, this is just a label that:
Ø takes over the caption of the form and;
Ø mimics the dragging of the form once you click with the mouse on that and drag the form.
· In the last steps there is a bindevent between the form’s resize event and the skincontrol cutframe method, as well as the forms resize event and the positioning of the buttons.
· Then the form is resized to cut away the frame.
This cutting away of the frame means that the borders and the titlebar are actually cut from the form. This is not the same as setting titlebar and borderstyle to 0 (zero), property settings that switch off the titlebar and borders.
All the pictures are subclasses of the SkinImage class. The name of the skin is passed as parameter, in the init of that class, the imagename is determined, based on the name of the class, and the correct image is added to the imagecontrol.
LPARAMETERS tcSkinName
WITH this
.top = 0
.left = 0
.BackStyle= 0
DO CASE
CASE UPPER(.Name) = "IMG1"
lcImage = "LEFTTOP.gif"
CASE UPPER(.Name)= "IMG2"
lcImage = "TOPBAR.BMP"
CASE UPPER(.Name)= "IMG3"
lcImage = "RIGHTTOP.gif"
CASE UPPER(.Name)= "IMG4"
lcImage = "RIGHTBAR.BMP"
CASE UPPER(.Name)= "IMG5"
lcImage = "RIGHTBOTTOM.gif"
CASE UPPER(.Name)= "IMG6"
lcImage = "BOTTOMBAR.BMP"
CASE UPPER(.Name)= "IMG7"
lcImage = "LEFTBOTTOM.gif"
CASE UPPER(.Name) = "IMG8"
lcImage = "LEFTBAR.BMP"
ENDCASE
.Picture = tcSkinName+"\"+lcImage
.Visible = .T.
Next, a caption is added, it takes the text from the form caption.
In the activateImages method the images are activated, basically this means that they will respond to mouse actions, here’s the code:
WITH thisForm
* pics 1 to 3 are the top 3 img's and the caption control.
* They will respond to the mousedown.
* basically this means they will make dragging of the form possible.
*
BINDEVENT( .img1, "MouseDown", this, "OnMouseDownMove")
BINDEVENT( .img2, "MouseDown", this, "OnMouseDownMove")
BINDEVENT( .img3, "MouseDown", this, "OnMouseDownMove")
BINDEVENT( .proxycaption, "mousedown", this, "OnMouseDownMove")
* images 4, 5 and 6 are the right side, bottomright and the bottombar.
* they make resizing of the control possible.
*
BINDEVENT( .img4, "MouseDown", this, "BeforeMouseDownResize")
BINDEVENT( .img4, "MouseMove", this, "AfterMouseDownResize")
BINDEVENT( .img5, "MouseDown", this, "BeforeMouseDownResize")
BINDEVENT( .img5, "MouseMove", this, "AfterMouseDownResize")
BINDEVENT( .img6, "MouseDown", this, "BeforeMouseDownResize")
BINDEVENT( .img6, "MouseMove", this, "AfterMouseDownResize")
This is the code of the OnMouseDownMove method:
LPARAMETERS tnButton, tnShift, tnXCoord, tnYCoord
* emulate the ability to drag the form using mouse
#DEFINE WM_SYSCOMMAND 0x112
#DEFINE WM_LBUTTONUP 0x202
#DEFINE MOUSE_MOVE 0xf012
IF tnButton = 1
ReleaseCapture()
SendMessage(ThisForm.HWnd, WM_SYSCOMMAND, MOUSE_MOVE, 0)
SendMessage(ThisForm.HWnd, WM_LBUTTONUP, 0, 0)
ENDIF
Here is the code for the BeforeMouseDownResize method:
LPARAMETERS tnButton, tnShift, tnXCoord, tnYCoord
IF tnButton=1
ThisForm.StartX = tnXCoord
ThisForm.StartY = tnYCoord
ENDIF
All it does is adding the coordinates to the startX and startY properties of the form.
The AfterMouseDownResize:
LPARAMETERS tnButton, tnShift, tnXCoord, tnYCoord
WITH ThisForm
IF tnButton=1 AND .ProxyBorderStyle=3
.Width = .Width + tnXCoord - .StartX
.Height = .Height + tnYCoord - .StartY
.StartX = tnXCoord
.StartY = tnYCoord
ENDIF
ENDWITH
The form is resized and startX and startY are refreshed.
Then the mousepointer is set for the images on the form:
* mousepointer with two arrowheads, pointing left and right
#DEFINE MP_WE 9
* mousepointer with two arrowheads, pointing left-up and right-down
#DEFINE MP_NW_SE 8
* mousepointer with two arrowheads, pointing Up and Down
#DEFINE MP_NS 7
WITH thisForm
.LockScreen = .T.
.img4.mousepointer = 0
.img5.mousepointer = 0
.img6.MousePointer= 0
IF .Borderstyle == 3
* only if the form is resizable.
*
.img4.mousepointer = MP_WE
.img5.mousepointer = MP_NW_SE
.img6.MousePointer= MP_NS
ENDIF
.lockScreen = .F.
ENDWITH
When the pictures are positioned this means that clockwise, the topleft, topbar, righttop, rightbottom, bottombar, leftBottom and leftbar are put in position.
The img1 doesn’t need positioning. When adding an object to a form its top left is always 0 (zero), there for we start with img2, being the topbar
WITH thisform.img2 && top bar
.Left = thisform.img1.Width
.Width = thisform.Width - thisform.img1.Width - thisform.img3.Width
.anchor = 11
ENDWITH
WITH thisform.img3 && top right corner
.Left = thisform.Width - .Width
.anchor = 9
ENDWITH
WITH thisform.img4 && right side bar
.Left = thisform.Width - .Width
.Top = thisform.img3.Height
.Height = thisform.Height - thisform.img3.Height - thisform.img5.Height
.anchor=13
ENDWITH
WITH thisform.img5 && right bottom corner
.Left = thisform.Width - .Width
.Top = thisform.Height - .Height
.anchor = 12
ENDWITH
WITH thisform.img6 && bottom bar
.Left=thisform.img7.Width
.Width = thisform.Width - thisform.img5.Width - thisform.img7.Width
.Top = thisform.Height - .Height
.anchor = 14
ENDWITH
WITH thisform.img7 && left bottom corner
.Top = thisform.Height - .Height
.anchor = 6
ENDWITH
WITH thisform.