I created a VBA macro that creates a Excel spreadsheet-based diagram in Visio. However, when I try to re-distribute the layout of the diagram, somehow the "End" label always ends up in the middle of the drawing, instead of being linear from "Start" to "End". This is the only problem: all connectors are OK, all shapes contain the required information.
Is there a way to have Visio ensure both Start & End are left in the corners of the diagram? I was thinking there might be a property in the "Start" & "End" that could drive this behavior.
Edit #1: Added what I have of code, although is not really fancy.
As of now, the only thing I'm using to distribute the diagram is the standard option:
Sub vsDiagramClosure()
'Layout Arrange
With vsPag
With .PageSheet
.CellsSRC(visSectionObject, visRowPageLayout, visPLOPlaceStyle).FormulaForceU = "1"
.CellsSRC(visSectionObject, visRowPageLayout, visPLORouteStyle).FormulaForceU = "5"
End With
.Layout
End With
End Sub
I adapted that from the macro recorder. vsPag was declared as Global vsPag As Visio.Page & it already has been assigned the proper page.
Thanks,
Related
I'm working on a Word Macro to streamline doing my University Mathematics and Statistics Coursework. Basically, selecting a group of equations and running it changes font size, line height and formats the paragraph in the way that I want to be common to all my maths/equations sections. It's great, but there's one little bit which I still have to do "manually", so to speak, which is right clicking and selecting "Align at equals".
Now the reason I'm asking this here and not on Super User is that I think I've exhausted all ways of doing this at the "record macro" stage. I found out how to access the right click menu without right clicking and accessed the "align at equals" option during record. Nothing was recorded.
Truth be told I'd prefer to code the lot anyway as it gives me more control. So, I'll post my code here and if anyone knows what line(s) of code I need to add to get it to replicate the "align at equals" command I would be extremely grateful.
Sub Equationiser()
'
' Equationiser Macro
'
'
With Selection.ParagraphFormat
.SpaceBefore = 12
.SpaceAfter = 12
.LineSpacingRule = wdLineSpace1pt5
End With
Selection.Font.Size = 20
End Sub
So, ideally just before the "With Selection.ParagraphFormat" section there would be some kind of "AlignAtEquals" command or whatever is needed so that, on one keypress, I would be able to align all the equals, set the line height to 1.5, place a 12 point space before and after the paragraph and change the font size to 20.
My absolute ideal would also be to programatically select all equation boxes that are in the same block, as "align at equals" is notoriously fussy and finicky as to when it will execute. That might also mean there may be a try and catch needed depending on whether trying to run "align at equals" when it wouldn't normally be available from the right click menu would do nothing or cause an error.
Any help on these two implementations would be gratefully appreciated but I'd happily settle for just the first.
I've built a solution that should address your needs, based on:
Looking through the equation to find the equal-sign.
Get the location of that equal-sign
Use that location to set the AlignPoint property of the OMath object
that is your equation
Use this MSDN reference if you want to explore more
Sub Equationiser()
'
' Equationiser Macro
'
'
Dim equationCounter As Long, charLoc As Long, FormattedTextLoc As Long
With Selection
For equationCounter = 1 To .OMaths.Count:
FormattedTextLoc = 0
For charLoc = 1 To Len(.OMaths(equationCounter).Range.FormattedText):
FormattedTextLoc = FormattedTextLoc + Len(.OMaths(equationCounter).Range.FormattedText.Characters(charLoc))
If .OMaths(equationCounter).Range.FormattedText.Characters(charLoc) = "=" Then
.OMaths(equationCounter).AlignPoint = (FormattedTextLoc - 1)
Exit For
End If
Next charLoc
Next equationCounter
End With
With Selection.ParagraphFormat
.SpaceBefore = 12
.SpaceAfter = 12
.LineSpacingRule = wdLineSpace1pt5
End With
Selection.Font.Size = 20
End Sub
I've done some brief testing that from what I can see this should be able to manage several code-blocks, i.e. when selecting 2 code-blocks it will align-at-equals all equations in the first block, then align-at-equals all equations in the second block (both blocks are not aligned with each oter) - is this your desired outcome to your request: My absolute ideal would also be to programatically select all equation boxes that are in the same block, as "align at equals" is notoriously fussy and finicky as to when it will execute.
I have a bunch of TextBox-Button pairs on a form. When the button is clicked I want to insert the value of the text box into a database. The name TextBoxes and Buttons follow a naming standard, for example Value1Tb - Value1Cmd and Value2Tb - Value2Cmd.
My problem is that since I want to do the same for every button I would like the possibility to write a Sub like:
Private Sub AnyButton_Click(sender As CommandButton)
Dim tb As TextBox
Set tb = GetTBByName(s.Name)
PutValueToDatabase(s.Name,tb.Text)
End Sub
But I cannot find a way to point the Click-event of a Button to a different sub than the standard Name_Click().
Anybody know a way around this, that doesn't involve me writing 50 or so different Name_Click() subs?
If you are OK to use Form Controls rather that ActiveX, as it looks as though you may be at the moment, then Chris' solution seems good.
However if you need ActiveX CommandButtons then you are unable (as the VBA compiler will tell you, "Procedure declaration does not match...") to have parameters in the callback for the click event, and you are unable to raise the event from multiple objects, although you do of course know which button raised the event (since the relationship is 1 CommandButton = 1 Sub).
So... I would go with something like:
Private Sub Value1Cmd_Click()
Call TheMethod(Value1Cmd)
End Sub
Private Sub Value2Cmd_Click()
Call TheMethod(Value2Cmd)
End Sub
Private Sub TheRealMethod(sender As CommandButton)
' Do your thing '
Dim tb As TextBox
Set tb = GetTBByName(s.Name)
PutValueToDatabase(s.Name,tb.Text)
' Etcetera... '
End Sub
Requires a stub for each button, so some copying and pasting to begin with, but then easy to maintain etcetera as all _Click event callbacks are pointing at the same method...
Edit:
E.g.
Sub AutoWriteTheStubs()
Dim theStubs As String
Dim i As Long
For i = 1 To 10
theStubs = theStubs & "Private Sub Value" & CStr(i) & "Cmd_Click()" & vbCrLf _
& " Call TheMethod(Value" & CStr(i) & "Cmd)" & vbCrLf _
& "End Sub" & vbCrLf & vbCrLf
Next i
Debug.Print theStubs
End Sub
It seems that what you want is to get the name of the clicked button. If you are creating buttons like this:
(where 'i' increments in a loop)
Set btn = Sheet1.Buttons.Add( , , , ,)
With btn
.OnAction = "btnSub"
.Caption = "Upadate"
.Name = "btn" & CStr(i) & "Cmd"
End With
and then defining a generic "private sub btnSub()" for all the buttons, you could at least get the name of the button that was clicked using Application.Caller. Something like:
Private Sub btnSub()
Dim ButtonName As String
ButtonName = Application.Caller
MsgBox ("Hello:" & ButtonName)
End Sub
Hope it helps!
I decided to make this an answer because I am doing something similar and I confirmed that it works.
You can store the OLEobjects in a Collection, of arbitrary size, containing Custom Class Objects that include the OLEobjects and associations and the events that you need. Thus you can completely avoid any code stubs.
Create a Custom Class to bind the Button and TextBox pairs.
Declare the Button object WithEvents.
Include your call-back in the exposed button event handler in the Class Module.
Put a Public routine in a Standard Module to initialise a Collection of these Custom Class objects by spanning the Form Controls. You can also use this to Add the controls programmatically as a 'reBuild' option. The Collection can be inside another Class Module with all of the management routines, but it needs to be Instantiated and loaded in a Standard Module.
Put a public routine in a standard module to receive the call-backs with whatever context you need. This can also be in a Worksheet Module if it makes for better encapsulation. You can use late binding to reference the callback or CallByName.
You need to bear in mind that the Module of the Form will recompile every time you add a control, so you have to be careful where you put your code.
My application has the controls directly on the Worksheet Surface, so I can't put the the Collection Class in, or source any initialisation of the Collection from the Worksheet module. This would amount to self modifying code and it grinds excel to a halt.
I dreamed this idea up through bloody-minded idealism (not necessarily a good thing) but, of course, I was not the first one to think of it as you can see here. #Tim Williams explains it in his answer. You can also google VBA Control Array Events to see plenty of similar examples including an excellent article by #SiddharthRout. In line with the VB6 analogy, he uses an Array instead of a Collection to achieve the same result.
I'll try to post some code later. My application is a bit different so it will take a lot of work to trim it down, but the principle is the same.
The other thing to bear in mind is that VBE really struggles with this type of thing so don't worry if it is loading up you processors. After you re-start with VBE off, all will be fine.
I have this same situation, and I just have a click event for every button that is a wrapper to the function I want to call. This also allows you to pass sheet-specific parameters if you need to.
Example:
Public Sub StoreButton_Click()
' Store values for transaction sheet 3/27/09 ljr
Call StoreTransValues(ActiveSheet)
End Sub
I just published (Open Source) the Event Centralizer for MSForms.
Citation: "The Event Centralizer for MSForms is a VBA programming tool that allows all sorts of custom grouping when writing handlers for the events occurring in UserForms.
With the Event Centralizer for MSForms, it is easy for example to have all TextBoxes react the same way when the Enter event occurs, or all except one, or only those with a particular Tag value.
Thanks to its events logs system, the Event Centralizer for MSForms is a powerful learning and debugging help."
I can't explain here how it works. I tried to do it on the site.
Set the event to =FunctionName(parameter).
A bit late but this may help someone else:
If you have a function called OpenDocumentById(docID as integer), then each control calling the function would have in the event the following:
cmd1's On Click event:
=OpenDocumentById([DocID])
cmd2's On Click event:
=OpenDocumentById([DocID])
etc...
I'm busy with some word automation and have run into an issue whereby a context menu within a document has items in, that I wish to remove.
Once the document is open, through vba I can remove these items by running the following code;
[VBA]
Dim oContextMenu As CommandBar
Dim oContextMenuItem As CommandBarControl
'Make changes to the ActiceDocument only (this is needed to make any changes to this document).
CustomizationContext = ActiveDocument
For Each oContextMenu In ActiveDocument.CommandBars
If oContextMenu.Type = MsoBarType.msoBarTypePopup Then 'Loop through all the context menus of type (msoBarTypePopup)
For Each oContextMenuItem In oContextMenu.Controls
If (InStr(oContextMenuItem.Caption, "Smokeball")) Then
oContextMenuItem.Delete
End If
Next
End If
Next
If I execute this code and check the document, all contextMenu sub items that contain the text "smokeball" are removed.
When I try move this code to my VB.NET solution (I have no choice of language, so VB it is), I get errors on the CustomizationContext = ActiveDocument line (this line has to be there for it to affect the current document).
The error I get is CustomizationContext' is not a by reference property.
Does anyone know how to get just that ONE line equivalent for vb.net?
Thanks in advance.
EDIT: In case you need to see the vb.net sub:
Private Sub RemoveUnwantedContextMenuItems()
Dim oContextMenu As CommandBar
Dim oContextMenuItem As CommandBarControl
'Make changes to the ActiceDocument only (this is needed to make any changes to this document).
WordApplication.CustomizationContext = WordApplication.ActiveDocument 'This is the error.
For Each oContextMenu In WordApplication.CommandBars
If oContextMenu.Type = MsoBarType.msoBarTypePopup Then 'Loop through all the context menus of type (msoBarTypePopup)
For Each oContextMenuItem In oContextMenu.Controls
If (InStr(oContextMenuItem.Caption, "Smokeball")) Then
oContextMenuItem.Delete()
End If
Next
End If
Next
End Sub
PS - I have also already tried using the .AttachedTemplate as well as .Normal / .NormalTemplate
Jules pointed me in the right direction with his sample code.
After lots of playing around I noticed that somewhere in the solution, the [TYPE] of WordApplication was getting changed to a dynamic type of sorts, hence, it couldn't use CustomizationContext.
My solution was this:
I changed this line;
WordApplication.CustomizationContext = WordApplication.ActiveDocument
To this:
CType(WordApplication, Microsoft.Office.Interop.Word.Application).CustomizationContext = WordApplication.ActiveDocument
Forcing the types to be correct.
Simple solution but took some time.
Thanks to Jules for pointing me in the right direction.
(Points should go to you).
I created my own Outlook form to use it as standard surface to enter certain orders instead of the normal message form. The creation, editing and sending works perfectly fine and in the next step I want to insert some code via VBA.
My problem is that I can´t access the objects of my form in the VBA editor. E.g. I want to show a message box when a certain checkbox is checked. According code would be:
Sub example()
If CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
When I run the code I get the error that the object could not be found. The same goes for every other object, like textboxes or labels etc.
I guess the solution is pretty simple, like putting Item. or sth. like that in front of each object. But so far I wasn't able to find the solution.
I´m using Outlook 2010.
I know this is a year too late but you'll want to do something like this example below. It's kinda a work around but you can get whatever value was selected.
Sub ComboBox1_Click()
Set objPage = Item.GetInspector.ModifiedFormPages("Message")
Set Control = objPage.Controls("ComboBox1")
MsgBox "The value in the " & Control.Name & _
"control has changed to " & Control.Value & "."
End Sub
You should be able to get the value, just get a handle on the object you want using the Inspector
The following is an excerpt from here
When you use a custom form, Outlook only supports the Click event for
controls. This is a natural choice for buttons but not optimal for
controls like the combo box. You write the code by inserting it into a
form’s VBScript editor. You need to have the Outlook form open in the
Form Designer and click the View Code button found in the Form group
of the Developer tab.
Sub CheckBox1_Click()
msgbox "Hello World"
End Sub
The code page is fairly minimal with no syntax highlighting. I just tried this now and it does work. Dont forget to Publish your form to pick up the new changes.
I know this is almost 6 years late but, in VB and VBA, simply start with the form name. (And if that doesn't work, just keep going up a parent object and you'll get there.) So, your code becomes:
Sub example()
If MYFORMNAME.CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
Of course, after typing "MYFORMNAME." you'll know if it will work because typomatic will kick in when the system recognizes "MYFORMNAME" after you hit the period.
I am trying to program an Excel module where it dynamically inserts code in new objects in a form that is created at design time.
I am using this code where "Code" contains a string with the actual code that should go into the DstrFiles object.
Dim DstrFiles As Object
Set DstrFiles = ThisWorkbook.VBProject.VBComponents("DistributeFiles")
With DstrFiles.CodeModule
.InsertLines .CountOfLines + 1, Code
End With
My problem is that when I use the .InsertLines, McAfee removes the entire Code from my module, is there a way to work around this?
First I create the label with:
Form1.Controls.Add("Forms.Label.1", "Label1", True)
Then I use the .InsertLines to create some code to go with the Label.
For instance, I want the background color of the label to turn red when someone clicks on it. This has been very easy to accomplish with the ".InsertLines".
An ugly way to work around this is to just create a bunch of code beforehand that is ready in the background and then limit the amount of labels that may be created on the fly. - I hope it won't come to that.
I have been googeling around, and this seems to be a known problem with McAfee.
Do anyone know a way to create a dynamic user form that can add code to new labels or button that are added with the Contrls.Add method?
You should not be generating new labels by writing code that creates the controls.
You should be using the .Add method on the Controls collection to create new labels.
For example:
UserForm1.Controls.Add("Forms.Label.1", "foo", True)
You can use WithEvents to get the events.
For example, in UserForm1,
Public WithEvents a As MSForms.Label
Private Sub a_Click()
MsgBox "label clicked"
End Sub
Private Sub CommandButton1_Click()
Set a = UserForm1.Controls.Add("Forms.Label.1", "foo", True)
a.Visible = True
a.Caption = "Hi There"
End Sub
If you want to make a dynamic array of newly added controls, you'll need to create a little wrapper class. Sample code for that is here.
If possible I would recommend against dynamic generation of code (smells like a self-modifying program?).
It's maybe hard to say without knowing your specific problem but I bet there is a better solution using a function with the necessary parameters.
You might be able to workaround this version of McAfee. But the next version of the data-files, or another malware blocker might block you anyhow.
So you can create code like this to run on you development machine, but it will never (or only temporary) work when distributed to customers.