Improving the "peg" handler - step 3
We already know how we want to improve the "page" handler - we want to be able to specify one of 4 possible position for the object within the
client area of the window. Start by creating a new project. Use the same names as before and add 4 buttons to the window. If you call the first one "btn0" and name the others accordingly, it'll make the code much more readable. Place the buttons anywhere you like on the screen but the result, when you run the program, should be to have them end up as shown in the llustration here.
The first thing we need to change is the actual call to the "pegObject" routine. Not only does it now have an additional parameter but there are 4 objects to "peg" and not just one as before, so our "OnResize" handler should look like this:
OnResize:
UseData winMainProcedure
Invoke pegObject, [hWnd], IDC_WINMAIN_BTN0, 0
Invoke pegObject, [hWnd], IDC_WINMAIN_BTN2, 2
Invoke pegObject, [hWnd], IDC_WINMAIN_BTN6, 6
Invoke pegObject, [hWnd], IDC_WINMAIN_BTN8, 8
Return (TRUE)
; End OnResize
EndU
There should be no big surprises here - we invoke the "pegObject" routine, pass it the address of the window (using the square brackets convention), give the name of the object to be pegged (using the "IDC" reference) and then tell it at which of the four corners that particular object is to be pegged. That's the easy bit - now we have to work out how to handle each of these instructions!
Let's start with the actual "signature" of the routine - only one thing has changed, remember. We'll call this new value "pegPosition" but the initial steps of setting up the local variables, getting the object's handle and then the size of the window's client area remain the same:
pegObject Frame hWnd, idControl, pegPosition
Local hButton, pBox : RECT
; 1. Get a handle on the button
Invoke GetWindowItem, [hWnd], [idControl]
Mov [hButton], Eax
; 2. Get the new size of the window's client area
Invoke GetClientRect, [hWnd], Addr pBox
OK, now we turn to the new parameter. We need to move the peg position value into one of the registers and, since Eax is currently employed, we should move the contents of "pegPosition" into Ebx:
; 3. Get the required peg position
Mov Ebx, [pegPosition]
We're now in a position to do something about the user's request to position an object at one of the four positions. What we are going to do is to repeatedly compare Ebx with the known values 0, 2, 6 and 8. Each time, if we find the zero flag set, we will jump to an appropriate label where the code for that pegging operation will be found. That means the JZ
mnemonic.
Now, here's the thing - in assembler, we can either jump forward or backwards and, in general, backwards jumps are more efficient. For that reason, if we simply say Jz .thisLabel
, goAsm will assume that there is a label with that name earlier in the code and it will fail for that reason. We get around this by means of the "greater than" and "less than" symbols to specify explicitly whether the jump is backwards or forwards, respectively. It's optional, in the case of backwards jumps, whether you use the symbol but it is mandatory when you want to jump forwards.
So, let's make 4 labels in our code at the end of the "OnResize" handler:
- .position_0
- .position_2
- .position_6
- .position_8
Go back to the point just after the point where we store the pegPosition in Ebx and put in the Cmp
and the corresponding Jz
jumps to those labels:
; 4. Jump to the relevant code
Cmp Ebx, 0
Jz >.position_0
Cmp Ebx, 2
Jz >.position_2
Cmp Ebx, 6
Jz >.position_6
Cmp Ebx, 8
Jz >.position_8
You don't actually have to indent the jump statements. I did that to make the relationship between the compares and the associated jumps.
Let's now turn to an important point. What would happen if the user passed the value "7" or "9" to the pegPosition routine? It would fail each one of these comparisons which means that after the last "Jz" it would move on to the next statement, which would be the first of the labels we defined above. That means it would execute the code for pegging the object at position "0". This is obviously not what we would want so we really need to put a statement after the last jump which would cause us to leave the routine if such a condition occurred:
; An incorrect "pegPosition" parameter has been supplied.
; Return to the main code immediately with a False flag.
Return (FALSE)
Note that you should return a "False" value because now that we understand comparisons and jumping, we can check for this flag whenever we have called the pegPosition routine to see if it succeeded or not and to handle a failure in some elegant fashion.
OK. Let's now turn our attention to the chunks of code which actually do the pegging.
Position 0
Pegging an object to Position 0 is really simple - it's at the top left of the client area so we simply have to offset it from 0,0 by the amount of border we want, say 4:
.position_0
Invoke SetLeft, [hButton], 4
Invoke SetTop, [hButton], 4
Return(TRUE)
None of that should pose problems for you but note the "True" in the Return statement to indicate success. Properly speaking, this is not really good enough. Either (or both) the SetLeft
or the SetTop
could conceivably fail. If you check EasyCode's documentation, you would see that failure of these methods causes a "False" to be returned in Eax, so really we should check that register after each call to these two methods and only if both returned a True should we return from pegPosition with a True value. However, I want to keep things simple, so let's leave things as they are.
Position 2
Position 2 is at the top right of the window. That means that the "y" value of the object is the same as if it had been pegged to position 0. The "x" value is just as we had it in the previous version of pegPosition - we take the width of the object, add the border value to it and then subtract the result from the width of the client area. So, taking the "x" or "left" position first, we would have:
.position_2
Invoke GetWidth, [hButton] ; Get the button's current width
Add Eax, [pegBorder] ; Add a border of 4
Mov Ebx, [pBox.right] ; Retrieve the parent's width
Sub Ebx, Eax ; Subtract the button's (plus border)
; width from it.
Invoke SetLeft, [hButton], Ebx ; Set the button's left position
; to the new value
Invoke SetTop, [hButton], 4 ; Set the top position
Return(TRUE)
Position 6
What do we have to do to place something at position 6? Since it is at the bottom left of the window, the "left" co-ordinate is the same as for position 0 and the "top" position of the object is as we had it in the previous version of the routine - take the height of the object, add the border and subtract the result from the height of the client area:
.position_6
Invoke SetLeft, [hButton], 4
Invoke GetHeight, [hButton] ; Get the button's current height
Add Eax, [pegBorder] ; Add a border of 4
Mov Ebx, [pBox.bottom] ; Retrieve the parent's height
Sub Ebx, Eax ; Subtract the button's (plus border) height
Invoke SetTop, [hButton], Ebx ; Set the button's top position to the new value
Return(TRUE)
Position 8
And lastly we come to our old friend - position 8. This is the one we used in our previous version of pegPosition. It's at the bottom right of the screen, so we have to work out both the "left" and "top" positions arithmetically:
.position_8
Invoke GetWidth, [hButton] ; Get the button's current width
Add Eax, [pegBorder] ; A border of 4
Mov Ebx, [pBox.right] ; Retrieve the parent's width
Sub Ebx, Eax ; Subtract the button's (plus border) width
Invoke SetLeft, [hButton], Ebx ; Set the button's left position to the new value
Invoke GetHeight, [hButton] ; Get the button's current height
Add Eax, [pegBorder] ; Add a border of 4
Mov Ebx, [pBox.bottom] ; Retrieve the parent's height
Sub Ebx, Eax ; Subtract the button's (plus border) height
Invoke SetTop, [hButton], Ebx ; Set the button's top position to the new value
Return(TRUE)
Putting it all together
And that's it, except for the EndF
you need to close the Frame. With the four invocations in the "OnResize" handler and the new code in pegPosition, we are good to go. Compile it and run it, after ensuring that you have 4 buttons in winMain.