I am working in AutoCad and with Excel VBA. In my code, I read in a Excel Worksheet the info that I need from the drawing including the Handle of the Entities I am interested on.
My next step is to verify some Acad Drawing info based on Excel calculations. For this, I pick the Entities Handle ID on Excel and it highlights the Entity on Acad. Some times Entities highlighting doesn't do enough contrast to differentiate the entity of my interest between all the other objects.
It would be better if the entity I need to verify get selected, as it is made in Acad environment with the mouse. Unfortunately, the practical way to verify the info is accessing by the Handle ID.
I have looked for alternatives in Internet and found something regarding with SelectionSets, but result was not different of just highlight the entity.
Any suggestion to select (as with the mouse) or to improve the colors or highlighting characteristics of the entities?
The code I am using is:
'''Sub dfSelHnd()
Dim actldwg As AcadDocument
Dim tAr(0) As AcadEntity 'Add items to selectionset must be done with arrays
Dim rng As Range
Dim txt As String
''---
''---
''---
Set rng = Selection
Set actldwg = AutoCAD.Application.ActiveDocument
txt = rng.Value '' txt is the HandleID
Set tAr(0) = actldwg.HandleToObject(txt)
Call zoomit(actldwg, tAr(0))
tAr(0).Highlight (True)
End Sub'''
Here the visualization examples:
Entity no selected, no highlighted
Entity "highlighted"
Entity selected
You can select entities in VBA by select group.
ThisDrawing.SendCommand ("_SELECT" + vbCr + "G" + vbCr + GroupName + vbCr + vbCr)
So first You need to create selectionset, then make a group from selectionset. Full sample would be:
Public Sub Test()
Dim ssh As AcadSelectionSet
Dim Ftyp(1) As Integer
Dim Fdat(1) As Variant
Dim BlockName As String
BlockName = "A-1"
Dim F1, F2 As Variant
Ftyp(0) = 0: Fdat(0) = "Insert"
Ftyp(1) = 2: Fdat(1) = BlockName
Set sstest = ThisDrawing.SelectionSets.Add("sstest")
F1 = Ftyp
F2 = Fdat
sstest.Select acSelectionSetAll, , , Ftyp, Fdat
Dim GroupName As String
GroupName = "sstest"
Dim group As AcadGroup
Set group = ThisDrawing.Groups.Add(GroupName)
For Each Item In sstest
group.AppendItems (Item)
Next
sstest.Delete
ThisDrawing.SendCommand ("_SELECT" + vbCr + "G" + vbCr + GroupName + vbCr + vbCr)
group.Delete
End Sub
This sample code mark selected blocks (by name). Now You should change the way of selection. But it's not the question, so I hope You will handle it easy.
Related
I'm trying to link shape data field from external data like excel.
As #JohnGoldsmith suggested I used DropLinked but "I'm getting object name not found" error.
My main agenda is drop multiple shapes on drawing with shape data field "Name", then fill all the shape data field using external data in order. I also used spatial search for dropping shapes on drawing(Thanks to #Surrogate). By the way I'm using Visio Professional 2019.
It's often a good plan to separate chained members so you can identify whether (as #Paul points out) you're having a problem getting to the stencil or the master.
Following is a modified example of link shapes to data. I've ditched all of the spatial search stuff as I think that's a separate issue. If you still have trouble with that I would ask another question and narrow your sample code to not include the data linking part - ie just drop shapes and try and change their position. Bear in mind there's also Page.Layout and Selection.Layout
I think you've got the adding the DataRecordsets in the other linked question, so this example makes the following assumptions:
You have a drawing document open
You have the "Basic Shapes" stencil open (note my version is metric "_M")
You have a DataRecordset applied to the document named "AllNames"
The above record set has a column named "Name" that contains the data you want to link
Public Sub ModifiedDropLinked_Example()
Const RECORDSET_NAME = "AllNames"
Const COL_NAME = "Name"
Const STENCIL_NAME = "BASIC_M.vssx"
Const MASTER_NAME = "Rectangle"
Dim vDoc As Visio.Document
Set vDoc = Application.ActiveDocument
Dim vPag As Visio.Page
Set vPag = Application.ActivePage
Dim vShp As Visio.Shape
Dim vMst As Visio.Master
Dim x As Double
Dim y As Double
Dim xOffset As Double
Dim dataRowIDs() As Long
Dim row As Long
Dim col As Long
Dim rowData As Variant
Dim recordset As Visio.DataRecordset
Dim recordsetCount As Integer
For Each recordset In vDoc.DataRecordsets
If recordset.Name = RECORDSET_NAME Then
dataRowIDs = recordset.GetDataRowIDs("")
xOffset = 2
x = 0
y = 2
Dim vStencil As Visio.Document
Set vStencil = TryFindDocument(STENCIL_NAME)
If Not vStencil Is Nothing Then
Set vMst = TryFindMaster(vStencil, MASTER_NAME)
If Not vMst Is Nothing Then
For row = LBound(dataRowIDs) + 1 To UBound(dataRowIDs) + 1
rowData = recordset.GetRowData(row)
For col = LBound(rowData) To UBound(rowData)
Set vShp = vPag.DropLinked(vMst, x + (xOffset * row), y, recordset.ID, row, False)
Debug.Print "Linked shape ID " & vShp.ID & " to row " & row & " (" & rowData(col) & ")"
Next col
Next row
Else
Debug.Print "Unable to find master '" & MASTER_NAME & "'"
End If
Else
Debug.Print "Unable to find stencil '" & STENCIL_NAME & "'"
End If
Else
Debug.Print "Unable to find DataRecordset '" & RECORDSET_NAME & "'"
End If
Next
End Sub
Private Function TryFindDocument(docName As String) As Visio.Document
Dim vDoc As Visio.Document
For Each vDoc In Application.Documents
If StrComp(vDoc.Name, docName, vbTextCompare) = 0 Then
Set TryFindDocument = vDoc
Exit Function
End If
Next
Set TryFindDocument = Nothing
End Function
Private Function TryFindMaster(ByRef vDoc As Visio.Document, mstNameU As String) As Visio.Master
Dim vMst As Visio.Master
For Each vMst In vDoc.Masters
If StrComp(vMst.NameU, mstNameU, vbTextCompare) = 0 Then
Set TryFindMaster = vMst
Exit Function
End If
Next
Set TryFindMaster = Nothing
End Function
The above code drops six shapes onto the page and adds a Shape Data row (Prop._VisDM_Name) with the corresponding data value. If you want the name text to appear in the shape then you would normally modify the master with an inserted field in the shape's text. (If you get stuck with this part then ask another question.)
One last point is that this example loops through the DataRecordset rows dropping a shape for each one, but there is also a Page.DropManyLinkedU method that allows you to this en masse.
So I have a form that executes a VBA script via a macro. The purpose of said script is to open Excel, create a new workbook, gather information from several tables and export them to a formatted spreadsheet. Each person has a sheet with their name, and the relevant data is printed in said sheet. It works perfectly for the most part. Only one problem... The table in Access where the name and demographics data is gathered from is formatted to sort by last name ascending alphabetically. The VBA script exports it in the order the names were entered. I want my VBA script to respect the formatting in the database table, and I would prefer not to have to add an alphabetizing subroutine to my VBA script.
Table A Format: ID, Active, Last, First, Role, Traveler, Resident, Preceptee, Phone, Completion
Table B Format: ID, Course, Course ID, Offered, HLC, Course Type
Last in Table A called "Roster" is the field with which I want my VBA script to sort alphabetically. The database is already configured to do this.
Thanks in advance!
VBA Code:
Option Compare Database
' This module exports the database to a spreadsheet with specific formatting when called from a Macro
' Each Employee will have a sheet named thier last name which will contain all HLC modules they have completed in a list
' It is specific to this Database, but can be adapted to others.
' Version 1.0 Stable
Public Function ExportXLS(TblA As String, TblB As String, Optional names As String, Optional specific As Boolean)
'****************'
'Set up variables'
'****************'
Dim ctrA As Integer
Dim ctrB As Integer
Dim var As Long
Dim str As String
Dim excel As Object 'Pointer to Excel Application
Dim book As Object 'Pointer to Excel Workbook
Dim sheet As Object 'Pointer to Excell Sheet
Dim Roster As DAO.Recordset
Dim Course As DAO.Recordset
Dim Child As DAO.Recordset
Dim last_name As DAO.Recordset 'Matrix of pointers that will hold parts of the tables to be printed to the corresponding Excel sheets
Dim course_name As DAO.Recordset 'Matrix of pointers that will hold parts of the tables to be printed to the corresponding Excel sheets
'********************************************************'
'Initialize our tables into thier recordsets for analysis'
'********************************************************'
Set Roster = CurrentDb.OpenRecordset(TblA)
Set Course = CurrentDb.OpenRecordset(TblB)
str = "SELECT Last FROM Roster"
Set last_name = CurrentDb.OpenRecordset(str)
str = "SELECT Course FROM [Course List]"
Set course_name = CurrentDb.OpenRecordset(str)
'**************************************************************************'
'Create the new excel file with default parameters and print the cover page'
'**************************************************************************'
Set excel = CreateObject("Excel.Application")
Set book = excel.Workbooks.Add
excel.Visible = True
Set sheet = book.Worksheets("Sheet1")
str = "Coversheet"
sheet.Name = str
sheet.Range("B2") = "HLC Database Export tool V1.0"
sheet.Range("B3") = "Written by Levi T Jackson, RN, BSN"
sheet.Range("B4") = "All rights reserved, Copyright 2021"
sheet.Range("B5") = "For use only by Emory Healhtcare, and others with permissions"
'**********************************'
'Main Loop, where the magic happens'
'**********************************'
ctrA = 0
Roster.MoveFirst
last_name.MoveFirst
Do Until last_name.EOF 'Move through the list of last names in the table Roster, one at a time
If Roster!Active = True Then 'No need to report on inactive employees, use access query for that
Set Child = Roster!Completion.Value 'Open a Recordset for the multivalued field Completion in Roster
ctrB = 1
If Child.EOF = True Then 'save the number of records for printing, or set to 0
var = 0
Else
Child.MoveLast
var = Child.RecordCount
Child.MoveFirst
End If
Course.MoveLast
If Child.EOF = False Then 'Avoid errors by not processing a page if no completion records exist
Set sheet = book.sheets.Add(After:=book.Worksheets(book.Worksheets.count)) 'For active employees, make a new sheet and switch to it, and set its name to the current last name from Roster
sheet.Activate
sheet.Range("A1").SELECT
str = Roster!Last & ", " & Roster!First
sheet.Name = str
sheet.Range("B2") = "Courses Completed"
Do Until Child.EOF 'If there are records in Completion for the current name, print them, move on when done
Course.MoveFirst
course_name.MoveFirst
Do Until Course.EOF
If Course![Course ID] = CInt(Child!Value.Value) Then
sheet.Range("D" & Mid(coordinates(ctrB), 2, Len(coordinates(ctrB)) - 1)) = Course![Course ID] 'prints course ID next to the name
sheet.Range("D2") = "'" & CStr(var) & " / " & CStr(Course.RecordCount) 'Prints number of records in completions
sheet.Range("B3") = "Course Name"
sheet.Range("D3") = "Course ID"
sheet.Range(coordinates(ctrB)) = Course!Course 'Prints course name
ctrB = ctrB + 1
Course.MoveLast
Course.MoveNext
Else
Course.MoveNext
course_name.MoveNext
End If
Loop
Child.MoveNext
Loop
End If
ctrA = ctrA + 1 'I might use this later in code updates, counts how manmy records are processed
Child.Close
excel.ActiveSheet.Cells.SELECT 'Selects all of the cells
excel.ActiveSheet.Cells.EntireColumn.AutoFit 'Does the "autofit" for all columns
sheet.Range("A1").SELECT 'Selects the first cell to unselect all cells
End If
Roster.MoveNext
last_name.MoveNext
Loop
'Clean up recordsets
last_name.Close
course_name.Close
Roster.Close
Set Roster = Nothing
Course.Close
Set Course = Nothing
End Function
'Converts the iteration of the print course sub loop into a sheet coordinate cell and returns it as a string
'This function is here so that later a more complicated printing coordinate system can be easily added as the database grows larger
Private Function coordinates(num As Integer) As String
coordinates = "B" & CStr(num + 4)
End Function
Add an order by clause to your OpenRecordset statements.
The single most annoying feature in Excel's built-in VBA editor is—in my opinion—the aggressive autoformatting of the code, which insists on rewriting what I have typed as soon as the cursor leaves the line. It is particularly distressing that the editor collapses all whitespace, thus preventing any meaningful code alignment. For example, if I try to align a sequence of assignments by the equals sign with values aligned by the decimal separator:
price = 10.01
quantity = 3.2
vat = 0.11
the editor inevitably scrambles it by collapsing all spaces:
price = 10.01
quantity = 3.2
vat = 0.11
Is there any way to avoid this kind unwelcome autoformatting?
Assignment cosmetics :-)
There's neither a special VBE property to change the VBE (autoformatting) options directly nor a way to do it programatically. - So afaik VBE irrevocably forces autoformatting upon the user by partial workarounds.
a) Class method
For the sake of the art and just for fun an actually (very) basic class approach to give you a starting idea; assignment arguments are passed as strings allowing any optical formatting - if that's what you really want:
Example call in current module
Sub ExampleCall()
Dim x As New cVars
x.Add "price = 11.11" ' wrong assignment
'...
x.Add "price = 10.01" ' later correction
x.Add "quantity = 1241.01"
x.Add "vat = 0.11"
Debug.Print "The price is $ " & x.Value("price")
End Sub
Class module cVars
Option Explicit
Private dict As Object
Sub Add(ByVal NewValue As Variant)
'split string tokens via equal sign
Dim tmp
tmp = Split(Replace(Replace(NewValue, vbTab, ""), " ", "") & "=", "=")
'Identify key and value item
Dim myKey As String, myVal
myKey = tmp(0)
myVal = tmp(1): If IsNumeric(myVal) Then myVal = Val(myVal)
'Add to dictionary
If dict.exists(myKey) Then
dict(myKey) = myVal
Else
dict.Add myKey, myVal
End If
'Debug.Print "dict(" & myKey & ") =" & dict(myKey)
End Sub
Public Property Get Value(ByVal myVarName As String) As Variant
'get variable value
Value = dict(myVarName)
End Property
Private Sub Class_Initialize()
'set (late bound) dict to memory
If dict Is Nothing Then Set dict = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set dict = Nothing
End Sub
Edit #1 as of 3/3 2021
b) Rem Evaluation method
Once again only for the sake of the art a way to read assignments entered into outcommented code lines via, yes via Rem (heaving a deep sigh for this archaic use originating from former Basic times) as it allows to format data with any wanted spaces or tabs and won't be mixed up hopefully with current outcommentings via apostrophe '.
This Test procedure only needs the usual declarations plus some assignment calls as well as the mentioned Rem part. Two simple help procedures get code lines, analyze them via a dictionary class cVars and eventually assign them.
Note that the following example
needs a library reference to Microsoft Visual Basic Extensibility 5.3 and
uses the unchanged class cVars of section a) simply to avoid rewriting it.
Option Explicit
Private Const THISMODULE As String = "Module1" ' << change to current code module name
Sub Test() ' procedure name of example call
'Declare vars
Dim price As Double: Assign "price", price
Dim quantity As Double: Assign "quantity", quantity
Dim vat As Double: Assign "vat", vat
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Enter assignments via Rem(ark)
'(allowing any user defined formatting therein)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rem price = 10.01
Rem quantity = 1241.01
Rem vat = 0.11
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Debug.Print quantity & " à $" & price & " = " & Format(quantity * price, "$#,##0.00")
End Sub
Help procedure Assign evaluating Rem codelines in procedure Test
Sub Assign(ByVal myVarName As String, ByRef myvar)
Const MyProc As String = "Test"
Dim codelines
getCodelines codelines, THISMODULE, ProcedureName:=MyProc
'Debug.Print Join(codelines, vbNewLine)
Dim x As New cVars ' set class instance to memory
Dim line As Variant, curAssignment
For Each line In codelines
curAssignment = Split(line, "Rem ")(1) ' remove Rem prefix from codelines
If curAssignment Like myVarName & "*" Then
x.Add curAssignment
myvar = x.Value(myVarName)
End If
Next
End Sub
Help procedure getCodelines
Called by above proc Assign. Returns the relevant Rem Codelines from the calling procedure Test. - Of course it would have been possible to filter only one codeline.
Sub getCodelines(ByRef arr, ByVal ModuleName As String, ByVal ProcedureName As String)
Const SEARCH As String = "Rem "
'a) set project
Dim VBProj As Object
Set VBProj = ThisWorkbook.VBProject
If VBProj.Protection = vbext_pp_locked Then Exit Sub ' escape locked projects
'b) set component
Dim VBComp As Object
Set VBComp = VBProj.VBComponents(ModuleName)
Dim pk As vbext_ProcKind
'd) get relevant code lines
With VBComp.CodeModule
'count procedure header lines
Dim HeaderCount As Long: HeaderCount = .ProcBodyLine(ProcedureName, pk) - .ProcStartLine(ProcedureName, pk)
'get procedure code
Dim codelines
codelines = Split(.lines(.ProcBodyLine(ProcedureName, pk), .ProcCountLines(ProcedureName, pk) - HeaderCount), vbNewLine)
'filter code lines containing "Rem" entries
codelines = Filter(codelines, SEARCH, True)
End With
'return elements
arr = codelines
End Sub
Don't forget to integrate the class module CVars from section a)!
Yeow... That was a mouthful.
I'm setting up a log to track inventory in the office. For some items we have multiple copies of the same tool. I've gotten the VBA to track the owners with userforms and vba, but the multiple-identical-names, first-occurance, blank cell trick is proving to be too much for me.
So what you have is this:
Item Serial Number Owner
Item A 999999999
Item A 999999991
Item A 999999992
Item B 22221
Item B 22222
Item B 22223
Item C hhhg77
Item C hhhg78
Item C hhhg79
I need the code to search for the ITEM name, gathered from ComboBox1 on the Userform, and find the first occurance of the ITEM WITHOUT an owner, (so the corresponding "OWNER" cell should be blank, and put the OWNER, gathered from ComboBox2 on the userform, in that spot.
I've been fooling around with Index & Match and VLookup and countless searches for "Finding First Row" but I've come up empty.
The ranges might be changed, so I'm hesitant to be so specific as to say search between A2:A4, so a search would be best, I think.
What I have so far is this....and it's weak, I apologize.
Public Sub FindBlankOwner()
Dim MultiItem As Range
Dim MultiOwner As Range
Dim ITEM As String
Dim OWNER As String
Dim MultiSerial As Range
Dim NO As Range
ITEM = ComboBox1.Value
STATUS = Application.WorksheetFunction.VLookup(ITEM, LUP, 6, False)
OWNER = ComboBox2.Value
Set ws = Worksheets("Owners")
Set MultiItem = Worksheets("Owners").Range("A1:A28")
Set MultiOwner = Worksheets("Owners").Range("C1:C28")
Set MultiSerial = Worksheets("Owners").Range("B1:B28")
Fillin = Evaluate("INDEX(MultiOwner, MATCH(ITEM, &
' Set FILLIN = Application.WorksheetFunction.Match(ITEM, (MultiItem), 0) And (Application.WorksheetFunction.Match(" ", (MultiOwner), 0))
' NO.Value = OWNER
'Set FILLIN = MultiItem.Find(What:=ITEM) And MultiOwner.Find(What:="")
End Sub
Search for Available Items Based on Empty Owner
I've written some raw code for your review. You were on the right track, but with the methods below we avoid using worksheet functions and instead use VBA methods.
First thing we want to do is find the first occurrence of the user-defined ITEM
After that we check if it has an OWNER or not. If it does, find the next ITEM. If it doesn't, assign the user-defined OWNER to the user-defined ITEM.
I've included some message boxes for clarity and convenience of the end-user.
The Code
Sub FindBlankOwner()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Owners")
Dim ITEM As String: ITEM = ComboBox1.Value
Dim OWNER As String: OWNER = ComboBox2.Value
Dim BlankOwnerFound As Boolean: BlankOwnerFound = False
Dim firstResult As String
Dim ItemRange As Range: Set ItemRange = ws.Range("A:A").Find(What:=ITEM, LookIn:=xlValues, LookAt:=xlWhole)
If Not ItemRange Is Nothing Then
firstResult = ItemRange.Address
Do
If Trim(ItemRange.Offset(0, 2).Value) = "" Then
ItemRange.Offset(0, 2).Value = OWNER
BlankOwnerFound = True
Else
Set ItemRange = ws.Range("A:A").FindNext(ItemRange)
End If
Loop While Not ItemRange Is Nothing And ItemRange.Address <> firstResult And BlankOwnerFound = False
Else: MsgBox "No results for " & ITEM & " found.", vbCritical, "Cannot Find " & ITEM
End If
If BlankOwnerFound = True Then
MsgBox OWNER & " has checked out " & ITEM & " with Serial Number " & ItemRange.Offset(0, 1).Value & ".", _
vbOKOnly, ITEM & " Check-Out Successful"
Else: MsgBox "No available " & ITEM & " in inventory.", vbCritical, "All " & ITEM & " are checked out."
End If
End Sub
NOTE: I haven't tested this code aside from reading it to myself to ensure it makes sense. There may be a couple errors (hopefully not) and if you can't solve them, please don't hesitate to let me know so we can work on it together. Let me know if this works for you :)
When controlling mainframe from Excel there are several functions one can use to navigate and scrape data. Some of these functions include, .GetString(x, y), .Putstring(x, y) and .MoveTo(x, y), just to name a few. (x denotes row #, y denotes column #, think of these like coordinates)
I have looked extensively online; (IBM redbooks, whitepapers, google, the stack, reflection api/vba manauls, tek-tips, websites strictly devoted to mainframe, etc)
One function I have been unable to find; a function that returns the cursor's current position within Mainframe's window.
Is there any way this can be done?
I figured out how to obtain cursor position by running the following code in reflection's VBE
Sub getCoordinates()
Dim ibmCurrentTerminal As IbmTerminal
Dim ibmCurrentScreen As IbmScreen
Dim returnValue As Integer
Set ibmCurrentTerminal = ThisFrame.SelectedView.control
Set ibmCurrentScreen = ibmCurrentTerminal.Screen
'---------------------------------------------------------------------
ibmCurrentTerminal.Productivity.ScreenHistory.ClearAllScreens
ibmCurrentTerminal.Productivity.RecentTyping.ClearAllItems
Dim CursorColumn As Integer
Dim valueColumn As Integer
Dim CursorRow As Integer
Dim valueRow As Integer
ibmCurrentScreen.CursorColumn = valueColumn
ibmCurrentScreen.CursorRow = valueRow
valueColumn = ibmCurrentScreen.CursorColumn
valueRow = ibmCurrentScreen.CursorRow
MsgBox "(" & valueRow & ", " & valueColumn & ")"
End Sub
This works if you want to retrieve cursor position, albeit via msgbox