PDA

View Full Version : Solved: moving a shape?



TrippyTom
07-15-2006, 03:33 PM
How do I:
A) paste an object while specifying a different width?
B) paste an object relative to the previous object (instead of the top left of the screen)?

Someone marked my previous post "solved" but it's really not. For details, go here: http://vbaexpress.com/forum/showthread.php?t=8784
(scroll down to the end)

Killian
07-15-2006, 06:13 PM
I had a look at the other post and put together some code. A routine that takes the arguments you specified, saves the original shape's proprty values, calculates the new size then loops the paste command, applying the new properties.
I think it should be quite easy to call from your form

Sub SplitShape(shp As Shape, vertical As Boolean, num As Long, gap As Single)
' shp: the shape to be split
' vertical: split vertical if true, horizontal if false
' num: desired number of shapes
' gap between slipt shapes
Dim newLeft As Single
Dim newTop As Single
Dim newWidth As Single
Dim newHeight As Single
Dim newShp As ShapeRange
Dim i As Long

'init variables for second shape
With shp
newLeft = .Left
newTop = .Top
newWidth = .Width
newHeight = .Height
.Copy
'calculate new
If vertical Then
newWidth = (.Width - (gap * (num - 1))) / num
.Width = newWidth
Else
newHeight = (.Height - (gap * (num - 1))) / num
.Height = newHeight
End If
End With
'copy, paste and resize
For i = 1 To (num - 1)
Set newShp = ActivePresentation.Slides(ActiveWindow.View.Slide.SlideIndex).Shapes.Paste
With newShp
If vertical Then
.Left = shp.Left + (gap + newWidth) * i
Else
.Top = shp.Top + (gap + newHeight) * i
End If
.Width = newWidth
.Height = newHeight
End With
Next i
ActiveWindow.Selection.Unselect

End Sub

Sub test()
' usage example

With ActiveWindow.Selection
If .Type = ppSelectionShapes Then
If .ShapeRange.Count = 1 Then
SplitShape .ShapeRange(1), False, 3, 10
End If
End If
End With

End Sub

TrippyTom
07-15-2006, 09:14 PM
Killian,

I can't thank you enough. I have stupid Office2007 installed at home, so I'm not sure it will work here, but I'm excited to try it when I get back to work.

In the meantime, I hope I can look at the code and understand it. The Powerpoint vba model is so hard to grasp because the help files SUCK! (oh yeah, i've said that before, haven't I)...

Anyway, THANKS! :bow:

TrippyTom
07-15-2006, 11:20 PM
K, you are a genius.
I never thought to treat it like a boolean. DOH! It seems so obvious now. I went through your code and learned a lot from it. It seems way more suave than what I could come up with.

Hopefully I will get to the level you are... someday. :)
Thanks again.

Just out of curiosity... would you mind explaining your code for me? I never thought to use paramaters but now that I've seen it done your way, it seems so logical. It seems I try to do everything the hard way.

I thought it would be easier for the user to specify the gap in inches, so I tried multiplying the gap by 72, but it's still not quite exact (probably because of the decimals). Is there any way I could fix that?

Killian
07-19-2006, 05:45 AM
Well, genius is overstating it somewhat :)
I'll explain my thinking in some detail, hopefully it's not too long-winded...

In any programming situation, writing actual code should be a relatively small part of the process. An analysis of the situation should first provide you with a clear idea of what you need to deal with (in terms of objects and varables) and the logical steps to get from A to B, as it were.
The code should then fall into place quite naturally, leaving you to concentrate on wrestling with the object model (which I agree, is often not much fun).

So, we know we are dealing with:
a single selected shape
some user options (variables)

our end product will require:
a calculation based on the user options
the creation of a number of identical shapes, repositioned according to our calculation

If we write a routine that contains all these, we can call it from anywhere in the project - the user form, a toolbar or pre-defined shortcuts. Assembling code is easier when its seperated cleany into specific routines, rather than dealing with everything in one continous stream.

The routine declaration will define all the paramaters it needs to do the jobSub SplitShape(shp As Shape, vertical As Boolean, num As Long, gap As Single)You need to make sure each of these is valid and the routine itself can deal with all possible values, so, if required, validation on the user input should be handled by the form, seperate from the routine's logic) :
shp As Shape:
before calling the routine, we check we have a single shape selected in the usage example. You could also perform other tests here to filter out other types, like pictures, for example. (These tests would actuallly be better placed around the code that calls the form, so you don't show it when it can't be used).
So, the specifics of our "known quantities" (even if they are variable in value)
vertical As Boolean:
any condition that can be defined as a choice of two options can be expressed as a boolean. It's best to work with the most simple data type possible - booleans are small and fast and given the choice (horizontal or vertical), it seems unlikely any other option will be needed in the future.
num As Long:
VBA works with Longs more efficiently the Integers, so any non-floating point number should use Long as the data type
gap As Single:
this is where the help files are useful. If you are applying a variable to an object property, look up that property in the help files/object explorer and see what data type it is. The Left, Top, etc properties use singles and we know that we'll use the "gap" variable in a calculation with those. It's best to calculate with identical data types.

I'm sure you get the idea with the calculation part of the code - looking at the pattern in what we're doing, we can see that effectively, the result is one shape (the original modifed) duplicated and repositioned. This process is repeated for the number of shapes required.

We can write some simple "pseudo-code" to formalise the logic:
With the original shape,
save it's properties (used in the calculation for the new shapes' dimensions)
copy it
if user option one,
re-calculate one property
else
re-calculate another property
apply the properties to shape one

for each extra shape required,
duplicate shape one
with the duplicate,
reposition it based on the new properties and the patternNow with the basic structure worked out and the specifics of the arguments established we can apply some syntax (in this case, VBA) to the logic and introduce the object model. Looking through our pseudo-code, we can see what's required - we don't actually need much...

With the original shape,
' With... End With to work with a specified object

save it's properties (used in the calculation for the new shapes' dimensions)
'assign properties to variables

copy it
'Copy method

if user option one,
'If... Then... Else to make the choice
re-calculate one property
' some simple math
else
re-calculate another property

apply the properties to shape one
'assign variables to properties

for each extra shape required,
'For... Next - we have "num" as the shape count
duplicate shape one
'Paste method into shapes

with the duplicate,
' With... End With to work with a specified object
reposition it based on the new properties and the pattern
'set object propertiesNow, I would suggest if you've got a reasonable grip on VBA syntax, we're at a point where the code practically writes itself! The only stumbling block will be in interacting with the OM. What we are looking to do in our loop is add a new shape to the active slide's shapes collection and adjust its position. A quick check of the help files tells us the Paste method as applied to the shapes collection returns a shaperange object (might be more than one shape pasted). With a couple of other expessions, we can write our one-liner that duplicates the shape and returns an object that we can refer to and update it's position' declare the object variable as shaperange (returned by Paste)
Dim newShp As ShapeRange

' we need to refer to a shapes collection to use paste in this way
ActivePresentation.Slides(current slide).Shapes.Paste

' this returns the index of the current slide
ActiveWindow.View.Slide.SlideIndex

' so this gives us
Set newShp = ActivePresentation.Slides(ActiveWindow.View.Slide.SlideIndex).Shapes.Paste
So that more or less represents the reasoning behind the code I posted. Frankly, just about any programming task can be tackled with this methodology. About 80% of the project time thinking things through, broad terms first then introducing specifics once they are clearly defined and checking out some of the details that crop up - 20% writing and debugging code.

Hope that helps.

Killian
07-19-2006, 06:05 AM
Regarding the conversion from inches - *72 is right, you need to make sure, as you suspected, you don't lose your decimals. Cast the textbox value to a single data type and only assign it the variables that support floating point numbers (single, double).'where
Dim gap As Single
'and txtGap is the name of a textbox control

gap = CSng(txtGap)

TrippyTom
07-19-2006, 09:18 AM
Thanks for your explanation K.

I guess where I have most difficulty is trying to learn what I can and can't do with objects (the Object Model). I think I have a grasp on the planning aspects pretty well, but then when I get to the Object Model, I discover I really can't do it the way I expected, then I have to go back to the drawing board. That frustrating.


VBA works with Longs more efficiently the Integers, so any non-floating point number should use Long as the data type
It's stuff like this that I would never have known without this forum. :) Why does VBA like Longs better?


... if required, validation on the user input should be handled by the form, separate from the routine's logic
Aha, so my if/then statements to handle the data types should be in the form code, not the actual procedure? I was kinda wondering about that. Thanks for clarifying.

In a related question, I'm putting all my macros in one file. Is it better to put each one in their own module, or have them in all in 1 module? I've been putting them all in one module, but now that I think of it, it's probably easier maintenance to have them in separate modules.

MOS MASTER
07-19-2006, 09:20 AM
Well, genius is overstating it somewhat :) .

Nonesense.. you now you're buddy! :)

Killian
07-20-2006, 02:55 AM
but then when I get to the Object Model, I discover I really can't do it the way I expectedYes, I often find my expectations dashed on the jagged rocks of one quirky object model or another. If I have a VBA project on, I generally have to spend a bit of time doing "proof-of-concept" tests alongside the design to make sure things are going to work out. (This is also a good time to test your proposed methods' compatability across different versions of Office)


Why does VBA like Longs better?Longs are 32-bit (as opposed to 16-bit for Integer). This is the native data type for the application, OS and CPU (even if you are running a 64-bit CPU and OS, until you move on to Office 12, you'll still be running a Win32 app) so a Long never has to be converted through compilation, execution, etc. An integer will have to be converted to a long at some stage.


so my if/then statements to handle the data types should be in the form code, not the actual procedure?I would put that test around the command that first calls the userform (so it only displays if you have a single valid shape selected). In the form code, you should check each of the fields you are passing to the routine are valid before you call it (what result will you get if the user enters a string in the "gap" field?)


Is it better to put each one in their own moduleThere are a couple of factors to consider here:
Organisation - The larger the project, the more relevant this becomes. I will generally have a module for initialization code (global variables, constants, initialization and exit routines), one for generic public functions, one for toolbars & menus and then start grouping by functionality.

Scope - more importantly, by declaring a variable at module level (outside the routines), you can "share" it with all the routines in that module. You can also have routines and functions available to one module and not the rest of the project.

@Joost: :beerchug: