I need a reference to a selected (via mouse click, it only needs to be one selection) shape in Visio. However the shape might be in a group.
I cant seem to get it to work when I select a shape in a group, the shp Object remains empty.
Sub selectTest()
Dim sel As Visio.Selection
Set sel = ActiveWindow.Selection
Dim shp As Visio.Shape
Set shp = sel.PrimaryItem
If Not shp Is Nothing Then
MsgBox "It worked"
Else
MsgBox "No shape in sel"
End If
End Sub
When the "Top-Level" Group is selected, it works.
When a shape inside the group, which might as well be a group itself, is selected, it doesn't.
When a shape which is not in a group is selected, it works again.
Context: I want to fire custom VBA code from the context menu. When you right click the shape, it is selected automatically.
How can I get a reference to a shape when it is in a group?
EDIT: To clarify further: Shapes inside my Document all have corresponding database entries. I want to (via XML) add a custom Delete button to the context menu (that works), this should call a deletemethod that gets the shape on which the method was called as a parameter so it can search for the corresponding DB entry and delete that (as well as the entries of any subshapes if the selected shape is a group) before the shape (and all of its subshapes) is deleted with shape.delete
Use Selection.IterationMode property to include sub-selected shapes in the selection
Set sel = ActiveWindow.Selection
sel.IterationMode = 0
Set shp = sel.PrimaryItem
I don't know Visio VBA, but give it a try:
UPDATE
Sub selectTest()
Dim x As Integer
Dim sel As Visio.Selection
Dim shp As Visio.Shape
Dim inner_shape As Visio.Shape
Set sel = ActiveWindow.Selection
Set shp = sel.PrimaryItem
For x = 1 To shp.Shapes.Count
Set inner_shape = shp.Shapes(x)
'// Do something with inner shape
Next
End Sub
Related
I'm trying to create a way for a word document to have certain textfields data to be replaced with other data. In my case, textfields are shown as a part of shapes and the textfields themselves don't have name's to them so I wanted to possibly do it by their shape ID. So for example I have a 5 Textboxes next to each other and say I want to edit the 4th textbox to say something since it's blank without affecting the other textboxes. What would I need to do?
Though Process: Because all the files have the same format, if I can figure out the id of that shape or textbox, I can directly reference that id and change the textfield that way. The text in the field is all random so I can't do a specific find word and replace so that's why I'm trying to do it by id or even just by having it do a count of the number of shapes on the page of a word document.
Tip: I turned on paragraph markers to see the textboxes more clearly.
Example of Code I've written so far:
Sub TextBox()
'find a specific textbox and edit it
Dim doc As word.Document, rng As word.Range
Dim shp As Shape, iShp As word.InlineShape
Set doc = ActiveDocument
Dim textbCount As String
Dim textbId As String
'textbCount = ActiveDocument.Shapes.Count
'textbId = oShape.ID
Dim sr As ShapeRange
Set sr = shp.TextFrame.TextRange.ShapeRange(5)
For Each shp In sr
If shp.ID = 0 Then
'oShape.TextFrame.TextRange.InsertAfter shp.ID
'shp.Delete
Debug.Print shp.Type
Debug.Print shp.ID
End If
Next shp
If ActiveDocument.Shapes.Count > 0 Then
For Each shp In ActiveDocument.Shapes
If shp.AutoShapeType = msoShapeRectangle Then
If shp.TextFrame.HasText = True Then
'shp.TextFrame.TextRange.GoToNext (wdGoToField)
'shp.Delete
'shp.Delete
shp.TextFrame.TextRange.InsertAfter textbCount
Exit For
End If
End If
Next shp
End If
End Sub
This is code you could use, I was able to just figure out the answer. What the code does is checks that the word document that you are trying to read is open and then it first checks to see if there are any shapes at all on the document which is the c > 0 because textboxes are categorized as shapes. Then it does a For Each loop going through all the shapes on the entire document and each shape has it's own unique identifier.
I already tested this for if templates that have the same format of textboxes, they will typically share the same identifier, so if you say have 2 word documents with each 20 textboxes and its a carbon copy of the other just with different text in the boxes almost like they took this blank document and then used it as the base template, it's highly likely that the ID's between the 2 documents are the same if opened separately, if they are combined into 1 document is when the ID's will change so that your not referencing the same data.
To continue on with the code, it will next check all the textboxes for a #, this can be changed out for anything, but for my case I wanted to find out which boxes by their ID I would be using since the word doc won't tell you, so because no where else on the document had #'s, I used those to find where the boxes were. Once you know the ID, you can just reference the boxes directly instead of using the #'s but you need to first know which ones have them.
Next the code will print to the "Immediate Window" which is like a debug window that you can open either in the view tab or by ctrl + G if your one windows and what it will print is the shape ID for each shape that has the # and then print whatever text is in that box which should include the # there along with whatever text is there in that box.
Now if you want to add text to the text box, I didn't include it in my example, or even replace the text. Just make an if statement for if shp.ID = 16 for example then inside that If Then statement say shp.TextFrame.TextRange.Text = "" or if you have a string you want to pass in, replace "" with whatever string that is and in the double quotes you can either leave that blank to make that textbox your referencing blank or you can put text in it to make it say something.
If your doing a project, like I was, and it requires checking a lot of these textboxes to reference the string to another textbox so basically one textbox determines the other. Use For Each shp In oShp a lot or your equivalent to that and check each ID and store it in a string variable and then do a separate For Each to reference those string variables to make new if statements or declarations since you you'll need to go through all the textboxes at least once to grab whatever data might be contained in them since it goes through the For Each sequence one at a time.
Dim shp As Shape
Dim oShp As Object
Dim doc As Document
Dim c As Integer
Dim objWord As Object
Dim objDoc As Document
'Set doc = ActiveDocument
Set objWord = GetObject(, "Word.Application")
objWord.Visible = True
Set objDoc = objWord.Documents.Open("C:\Users\word.docx") 'Set this to wherever the word file is located along with the name of the word file so "C:\Users\worddoc.docx" is an example you could do
'Set objDoc = objWord.ActiveDocument
Set doc = objWord.ActiveDocument
Set oShp = doc.Shapes
c = ActiveDocument.Shapes.Count
'Set text1 = shp.TextFrame.TextRange
If c > 0 Then
For Each shp In oShp
If InStr(shp.TextFrame.TextRange.Text, "#") Then
Debug.Print shp.ID
Debug.Print shp.TextFrame.TextRange.Text
End If
Next shp
Debug.Print c
End If
I want to highlight the shape corresponding to a particular group. The following code is only highlighting shapes that are grouped with active page or master but not with the group present in the active page.
Sub CA_Trace_Conflict1()
PCC_CA = InputBox("Enter PCC Band")
'SCC1_CA = InputBox("Enter SCC1 Band")
Dim shp As Visio.Shape
Dim subshp As Visio.Shape
Dim connectorshape As Visio.Shape
Dim BandLinewidth As String
Dim lngShapeIDs() As Long
Dim count As Integer
Dim PCC_Flag As Integer
Dim SCC1_Flag As Integer
PCC_Flag = 0
SCC1_Flag = 0
Dim DiagramServices As Integer
DiagramServices = ActiveDocument.DiagramServicesEnabled
ActiveDocument.DiagramServicesEnabled = visServiceVersion140 + visServiceVersion150
Dim UndoScopeID1 As Long
PCC_CA_space = PCC_CA & " "
For Each shp In Visio.ActivePage.shapes
If shp.Type = 2 Then 'Check if shp is a group
For Each subshp In shp.shapes
If InStr(shp.Text, PCC_CA_space) > 0 Then
'If PCC_CA Like shp.Text Then
Set connectorshape = shp
Debug.Print shp.Parent
Application.ActiveWindow.Page.shapes.ItemFromID(shp.ID).CellsSRC(visSectionObject,visRowLine, visLineWeight).FormulaU = "5.5 pt"
' Debug.Print shp.ID
End If
Next
End If
Next
End Sub
I think you want to select a subshape within a group programmatically. Doing this in Visio is not obvious, so let me help. I'll put links to two articles on my website, plus one on Microsoft's at the end of the post. These discuss selection-related topics in further detail.
So let's tackle your problem...
Setup
Open a blank drawing in Visio
Draw two rectangles, then group them
You now have three shapes on this page.
Sheet.1 is a subshape
Sheet.2 is a subshape
Sheet.3 is the group
You can programmatically select the group like this, as you've discovered:
Public Sub SelectGroup()
'// Get the active window:
Dim win As Visio.Window
Set win = Visio.ActiveWindow
'// Deselect everything:
Call win.DeselectAll
'// Get a shape object:
Dim shp As Visio.Shape
Set shp = Visio.ActivePage.Shapes.ItemFromID(3) '<<----- Sheet.3 is the group!
'// Cause that shape to be selected in the window:
Call win.Select(shp, Visio.VisSelectArgs.visSelect)
'// Cleanup:
Set shp = Nothing
Set win = Nothing
End Sub
By the way, the Sub above is much more nitpicky and long than it has to be. But it will help to have things simple and clean, when you start adding features and behaviors. You can actually one-line the whole procedure like this--you can even paste this into the Immediate window:
Call Visio.ActiveWindow.Select(Visio.ActivePage.Shapes.ItemFromID(3), Visio.VisSelectArgs.visDeselectAll + Visio.VisSelectArgs.visSelect)
Now to subselect Sheet.1 or Sheet.2. One would think we could simply change the shp object to be one of the subshapes, ala:
'// Sheet.1 is a subshape, you'll get an error
Set shp = Visio.ActivePage.Shapes.ItemFromID(1) '<<----- ID = 1
but this won't work. In fact you'll get an "Inappropriate target object for this action" error.
To fix this, we have to pass a different argument to the Select method:
Public Sub SelectSubshape()
'// We've drawn two rectangles on a blank page, then
'// grouped them. Sheet.1 and Sheet.2 are subshapes,
'// Sheet.3 is the group.
'// Get the active window:
Dim win As Visio.Window
Set win = Visio.ActiveWindow
'// Deselect everything:
Call win.DeselectAll
'// Get a subshape object:
Dim shp As Visio.Shape
Set shp = Visio.ActivePage.Shapes.ItemFromID(2)
'// Cause that shape to be SUBSELECTED in the window.
'// Note the different argument: visSubSelect
Call win.Select(shp, Visio.VisSelectArgs.visSubSelect) ' <<------ visSubSelect!
'// Cleanup:
Set shp = Nothing
Set win = Nothing
End Sub
Voila! Subshape selected in the active window!
If you want to detect which shapes are already selected, then you'll have to fiddle with the IterationMode property of a Selection object. This is pretty confusing, plus I don't think you're asking for that right now. But knowing the term will help you search for help in the future, should you need it.
Articles
Getting a Handle on Selecting and Subselecting Visio Shapes
Detect Sub-selected Shapes Programmatically
Selection.Select method (Visio)
I have the following code:
Sub CommandButton1_Click()
Dim NoIO As String
Dim shp1 As Visio.Shape
Dim i As Integer
Set shp1 = Application.ActivePage.Shapes(1)
NoIO = ComboBox1.Value
If NoIO = "7" Then
MsgBox shp1.id
'Target shape id selected'
'Change shape data of that shape'
End If
Unload Me
End Sub
Whenever a shape is dropped onto the screen, a user form is presented to the user. When it is submitted, this code runs.
Currently, I can only output the ID of the shape first dragged onto the screen shown by this line:
Set shp1 = Application.ActivePage.Shapes(1)
How can I change this so that the ID of the shape dragged onto the screen is shown instead?
Thank you
If you are using EventDrop handler and a ShapeSheet cell, you can just pass shape ID to your function. You could use something like the formula below (where ID() is a built-in function returning shape ID. You can use it to get the shape from the OnDrop handler defined in your VBA code. "&" is used to concatenate text strings in VBA:
RUNMACRO("ThisDocument.OnDrop("& ID() &")")
And then in VBA:
Sub OnDrop(shapeId)
Debug.Print shapeId
Set shape = ActiveDocument.Shapes.ItemFromID(shapeId)
' do something with the shape
End Sub
Better, you can use CALLTHIS instead of RUNMACRO (it always passes subject shape as a first parameter)
CALLTHIS("ThisDocument.OnDrop")
And then in VBA:
Sub OnDrop(x As Shape)
Debug.Print shape.ID
' do something with the shape
End Sub
In the event handler, before you show the form, you need to remember the shape, and then you can pass it to the form.
Please note that I assume in the above examples that "OnDrop" is defined in "ThisDocument". If it's defined in a module, you don't need "ThisDocument." prefix
Yet another option could be to handle the "Shape Added" event in the VBA instead of specifying the ShapeSheet formula. Your event handler receives shape being dropped as a parameter in this case.
I work from excel, and i open several powerpoint in a loop inside a directory, in vba.
While i run the macro (from excel), I make a loop within every shape inside every slides in powerpoints presentation.
I stop the macro to see if AutoShapeType = -2, and i select it in vba just to check visualy if the shape is the right shape i want.
Otherwise, if the selected shape doesn't seems right, I select it manually and I want to know how the syntaxe to get the id of the current selected shape, in order to name it.
Set PPtapp = CreateObject("Powerpoint.Application")
PPtapp.Visible = True
Dim sld As Slide
Dim numslide As Long
Dim nbslide As Long
Dim WVL_CptShape As Integer
'list of every powerpoint path
ppt = ThisWorkbook.Worksheets("Template").Range("A" & i).Value
For i = 2 To ThisWorkbook.Worksheets("Template").Range("A65536").End(xlUp).Row + 1
Set PptDoc = PPtapp.Presentations.Open(ppt)
With PptDoc
For Each sld In PptDoc.Slides
For WVL_CptShape = 1 To .Slides(sld.SlideNumber).Shapes.Count
WVL_Id = .Slides(sld.SlideNumber).Shapes(WVL_CptShape).ID
If PptDoc.Slides(sld.SlideNumber).Shapes(WVL_CptShape).AutoShapeType = -2 Then
'I select the shape to see visualy if it's a good selection and I stop the macro
PptDoc.Slides(sld.SlideNumber).Shapes(WVL_CptShape).Select
Stop
'if the selection doesnt seems right I select the right shape manualy
'Question : in vba, i want to change the name of the selected shape.
'But i don't know how to get the id of the current selected shape (see below : ID_OF_CURRENT_SHAPE_SELECTED_MANUALY)
'I would like to rename it, in order to recognize it easily next time
PptDoc.Slides(sld.SlideNumber). Shapes(ID_OF_CURRENT_SHAPE_SELECTED_MANUALY).Selection.Name = "Myshape"
end if
Next WVL_CptShapeNext
sld.Close
End With
Next
PPtapp.Quit
Set PPtapp = Nothing
Set PptDoc = PPtapp.Presentations.Open(ppt)
With PptDoc
For Each sld In PptDoc.Slides
For WVL_CptShape = 1 To sld.Shapes.Count
WVL_Id = sld.Shapes(WVL_CptShape).ID
If sld.Shapes(WVL_CptShape).AutoShapeType = -2 Then
'I select the shape to see visualy if it's a good selection and I stop the macro
sld.Shapes(WVL_CptShape).Select
Stop
' And to change the name of the shape:
sld.Shapes(WVL_CptShape).Name = "New name for shape"
' or better, in case you selected a different shape:
ActiveWindow.Selection.ShapeRange(1).Name = "New name for shape"
In this type of situation, you want to work with the shape's Index, not its ID.
Note that you can iterate through the shapes collection on a slide just as you can iterate through the slides collection in a presentation. It makes the code a lot simpler to write and to follow:
Set PptDoc = PPtapp.Presentations.Open(ppt)
With PptDoc
For Each sld In PptDoc.Slides
For each shp in sld.shapes
If shp.AutoShapeType = -2 Then
'I select the shape to see visualy if it's a good selection and I stop the macro
shp.Select
Stop
Can I get the name of the layer a connector is assigned to in vba?
I am doing this with shapes like this
ActivePage.Shapes(1).layer(1)
And now I am looking for something similar but for connectors.
I'm not completely clear on your issue, but there's nothing special about connectors - they're still just shapes. So if you know the index of your target shape and layer, then you can use your code above.
Shapes in Visio can also belong to multiple layers (layers don't work in the same way as, say, Photoshop), so you might want to get the layer count first and then run through each one. For example:
Sub CheckLayers()
Dim shp As Visio.Shape
Dim i As Integer
For Each shp In ActivePage.Shapes
Debug.Print shp.NameU
For i = 1 To shp.LayerCount
Debug.Print " " & shp.Layer(i).Name
Next i
Debug.Print ""
Next shp
End Sub
Also, depending on what you're trying to do, you might want to take advantage of the Page.CreateSelection method to return a selection based on shapes on a particular layer. Here's a very slightly modified version from the Visio SDK:
Public Sub CreateSelection_Layer_Example()
Dim vsoLayer As Visio.Layer
Dim vsoSelection As Visio.Selection
Set vsoLayer = ActivePage.Layers.ItemU("Connector")
Set vsoSelection = ActivePage.CreateSelection(visSelTypeByLayer, visSelModeSkipSuper, vsoLayer)
'Note that you don't have to pass the selection object to
'the ActiveWindow Selection property - you can just work
'with it directly if you want to
Application.ActiveWindow.Selection = vsoSelection
End Sub
If you're using the Dynamic Connector, this gets automatically assigned to a layer called 'Connector'.