Sub Initialize
'Copyright Botstation (www.botstation.com)
Dim session As New NotesSession
Dim wks As New NotesUIWorkspace
Dim db As NotesDatabase
Dim view As NotesView
Dim uiView As NotesUIView
Dim doc As NotesDocument
Dim column As NotesViewColumn
Dim row As Long,colcounter As Long,arrcnt As Long,arrcounter As Long, x As Long
Dim filename As String, currentvalue As String
Dim rowsatonce As Integer,cn As Integer
Dim xlApp As Variant, xlsheet As Variant,xlwb As Variant, xlrange As Variant, tempval As Variant
Dim DataArray
Dim VColumns List As String
ReDim DataArray(0, 80) As String
'80 columns is our expected max number of columns in the view. It's dynamically recomputed below to actual (lower) number. Change if the number of columns is larger.
Set db=session.CurrentDatabase
Set xlApp = CreateObject("Excel.Application")
xlApp.Visible = True 'Excel program is visible (to avoid errors and see what is happening)
Set xlwb=xlApp.Workbooks.Add
Set xlsheet =xlwb.Worksheets(1)
Set uiView = wks.CurrentView
Set view = db.GetView( uiView.ViewName ) ' get the view currently open in UI
arrcnt=0
row=1
colcounter=0
rowsatonce=20
ForAll c In view.Columns
If c.isIcon<>True Then ' do not include icon columns
If c.Formula<>"""1""" And c.Formula<>"1" Then 'do not include columns which are used for counting docs (Total)
colcounter=colcounter+1
DataArray(row-1, colcounter-1) =c.Title
VColumns(CStr(cn))=CStr(cn)
End If
End If
cn=cn+1
End ForAll
ReDim Preserve DataArray(0, colcounter-1) As String
xlsheet.Range("A1").Resize(1, colcounter).Value = DataArray ' set column names
ReDim DataArray(rowsatonce-1, colcounter-1) As String
row=2
x=0
Set doc = view.GetFirstDocument
While Not ( doc Is Nothing )
ForAll col In VColumns
currentvalue=""
tempval= doc.ColumnValues(Val(col))
If IsArray(tempval) Then
ForAll v In tempval
If currentvalue="" Then
currentvalue=v
Else
currentvalue=currentvalue+","+v
End If
End ForAll
Else
currentvalue=tempval
End If
x=x+1
DataArray(arrcounter, x-1) =currentvalue
End ForAll
x=0
row=row+1
arrcounter=arrcounter+1
If arrcounter/rowsatonce=arrcounter\rowsatonce And arrcounter<>0 Then
xlsheet.Range("A"+Cstr(arrcnt*rowsatonce+2)).Resize(rowsatonce, colcounter).Value = DataArray
arrcnt=arrcnt+1
arrcounter=0
ReDim DataArray(rowsatonce-1, colcounter-1) As String
End If
Set doc = view.GetNextDocument (doc)
Wend
If arrcounter/rowsatonce<>arrcounter\rowsatonce And arrcounter>0 Then
' Redim Preserve DataArray(arrcounter, colcounter-1) As String
xlsheet.Range("A"+Cstr(arrcnt*rowsatonce+2)).Resize(arrcounter, colcounter).Value = DataArray
End If
MsgBox "Done"
End Sub
Once you have got the month that you want to include you can add a condition after this line:
While Not ( doc Is Nothing )
Compare the month (and probably year) with the (date) item on the document. You might need the NotesDateTime class to do this.
To filter the right month you can do this: (assuming you also need the year)
If year(date1) * 100 + month(date1) = year(date2) * 100 + month(date3)
Related
I'm building an automatic calendar project.
Input information is a task list for the month/year and an idea is to distribute exact values from the input list randomly over the output range.
I thought using a recursive vba function
Sub distributeRandomly()
Dim InRng as Range
Dim OutRng as Range
Dim distributePercent as Single
Set InRng=Application.InputBox("Select input range",Type:=8)
Set OutRng=Application.Input("Select output range",Type:=8)
distributePercent=InRng.count/OutRng.count
distributeRandomlyRec(InRng,OutRng,distributePercent)
End Sub
Function distributeRandomlyRec(InRng As Range,OutRng as range,distributePercent as Single)
Dim i1 As Integer
Dim i2 As Integer
if OutRng.count=0 or InRng.count=0 Then
Exit Function
Else
Randomize
If distributePercent <= Rnd Then
Randomize
i1=int(OutRng.count*Rnd+1)
Randomize
i2=int(InRng.count*Rnd+1)
OutRng.Cells(i1).value=InRng.Cells(i2).value
##Here i stacked to define a new ranges without the choosen cells i1 and i2
##Maybe convert old range to array , delete a value and then
##Transpose() an array to a new range ?
##NewOutRng = ?
##NewInRng = ?
if NewOutRng.count=0 or NewInRng.count=0 Then
Exit Function
else
distributePercent=NewInRng.count/NewOutRng.count
distributeRandomlyRec(NewInRng,NewOutRnge,distributePercent)
End If
End Function
fixed the code - no recursive version , tested
Sub distributeRandomly()
Dim InRng As Range
Dim OutRng As Range
Dim inCollection As New Collection
Dim outCollection As New Collection
Dim i1 As Integer
Dim i2 As Integer
Dim distributePercent As Single
Set InRng=Application.InputBox("Select input range",Type:=8)
Set OutRng=Application.InputBox("Select output range",Type:=8)
Dim c As Range
For Each c In InRng
inCollection.Add c.Value
Next
While inCollection.Count > 0
If (OutRng.Count - outCollection.Count) > 0 Then
Randomize
distributePercent = inCollection.Count / (OutRng.Count - outCollection.Count)
If distributePercent >= Rnd Then
Randomize
i1 = Int(inCollection.Count * Rnd + 1)
outCollection.Add inCollection(i1)
inCollection.Remove (i1)
Else
outCollection.Add (vbNullString)
End If
End If
Wend
For i2 = 1 To outCollection.Count
OutRng.Cells(1, i2).Value = outCollection(i2)
Next
End Sub
I had previously asked about how to evenly distribute the items in n lists into a single list and was referred to this question: Good algorithm for combining items from N lists into one with balanced distribution?.
I made a practical example of my solution for this in VBA for Excel, since my application for this was resorting my Spotify lists which can be easily pasted into Excel for manipulation. Assumptions are that you have a headerless worksheet (wsSource) of songs with columns A, B, C representing Artist, Song, SpotifyURI respectively, a "Totals" worksheet (wsTotals) containing the sum of songs for each Artist from wsSource sorted in descending order, and a "Destination" worksheet where the new list will be created. Could I get some suggestions to improve this? I was going to get rid of the totals worksheet and have this portion done in code, but I have to go and I wanted to go ahead and put this out there. Thanks!
Sub WeaveSort()
Dim wb As Workbook
Dim wsDest As Worksheet
Dim wsSource As Worksheet
Dim wsTotals As Worksheet
Dim i As Integer
Dim iLast As Integer
Dim iDest As Integer
Dim iSource As Integer
Dim iOldRow As Integer
Dim iNewRow As Integer
Dim dDiff As Double
Dim dDiffSum As Double
Set wb = ThisWorkbook
Set wsTotals = wb.Worksheets("Totals")
Set wsSource = wb.Worksheets("Source")
Set wsDest = wb.Worksheets("Dest")
iLast = wsTotals.Range("A1").End(xlDown).Row - 1
For i = 2 To iLast
iSource = wsTotals.Range("B" & i).Value
iDest = wsDest.Range("A99999").End(xlUp).Row
If i = 2 Then
wsDest.Range("A1:C" & iSource).Value2 = wsSource.Range("A1:C" & iSource).Value2
wsSource.Range("A1:C" & iSource).Delete (xlShiftUp)
GoTo NextI
End If
dDiff = iDest / iSource
dDiffSum = 0
iNewRow = 0
For iOldRow = 1 To iSource
dDiff = iDest / iSource
dDiffSum = dDiffSum + dDiff
iNewRow = Round(dDiffSum, 0)
wsSource.Rows(iOldRow).Copy
wsDest.Rows(iNewRow).Insert xlShiftDown
iDest = iDest + 1
Next iOldRow
wsSource.Range("A1:C" & iSource).Delete (xlShiftUp)
NextI:
Next i
End Sub
Great question! I would take an object oritentated approach. Also I didn;t think it was clear what the logic was so here is my answer. Two classes and one normal module. Save these separately with the filenames ListManager.cls, List.cls, tstListManager.bas
So the ListManager.cls is this
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ListManager"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private mdic As Object
Public Sub Initialise(ByVal vLists As Variant)
Set mdic = VBA.CreateObject("Scripting.Dictionary")
Dim vListLoop As Variant
For Each vListLoop In vLists
Dim oList As List
Set oList = New List
oList.Initialise vListLoop, ""
mdic.Add mdic.Count, oList
Next
End Sub
Public Function WeaveSort() As Variant
Dim dicReturn As Object
Set dicReturn = VBA.CreateObject("Scripting.Dictionary")
Dim oNextList As List
Set oNextList = Me.WhichListHasLeastProgress
While oNextList.PercentageDone <= 1
Dim vListItem As Variant
vListItem = oNextList.GetListItem
dicReturn.Add dicReturn.Count, vListItem
oNextList.MoveNext
Set oNextList = Me.WhichListHasLeastProgress
Wend
Dim vItems As Variant
vItems = dicReturn.Items
'I don't like this bit
ReDim vRet(1 To dicReturn.Count, 1 To 1)
Dim lLoop As Long
For lLoop = 0 To dicReturn.Count - 1
vRet(lLoop + 1, 1) = vItems(lLoop)
Next lLoop
WeaveSort = vRet
End Function
Public Function WhichListHasLeastProgress() As List
Dim vKeyLoop As Variant
Dim oListLoop As List
Dim oLeastProgress As List
For Each vKeyLoop In mdic.keys
Set oListLoop = mdic.Item(vKeyLoop)
If oLeastProgress Is Nothing Then
'nothing to compare yet
Set oLeastProgress = oListLoop
Else
If oListLoop.PercentageDone < oLeastProgress.PercentageDone Then
'definitely take this new candidate
Set oLeastProgress = oListLoop
ElseIf oListLoop.PercentageDone = oLeastProgress.PercentageDone And oListLoop.Size > oListLoop.Size Then
'close thing, both showing equal progress but we should give it to the one with the bigger "queue"
Set oLeastProgress = oListLoop
Else
'no swap
End If
End If
Next
'return the answer
Set WhichListHasLeastProgress = oLeastProgress
End Function
and the List.cls file is
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "List"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private mvList As Variant
Private mlCount As Long
Private mlCursor As Long
Private mvName As Variant
Public Function Initialise(ByRef vList As Variant, ByVal vName As Variant)
Debug.Assert TypeName(vList(1, 1)) <> "" ' this will break unless you specify a 2d array
Debug.Assert LBound(vList, 1) = 1 ' this ensure you got it from a sheet
mvList = vList
mlCount = UBound(mvList)
mlCursor = 1
mvName = vName
End Function
Public Function GetListItem()
GetListItem = mvList(mlCursor, 1)
End Function
Public Function Name() As Variant
Name = mvName
End Function
Public Function MoveNext() As Boolean
mlCursor = mlCursor + 1
MoveNext = (mlCursor < mlCount)
End Function
Public Function Size() As Long
Size = mlCount
End Function
Public Function PercentageDone() As Double
PercentageDone = mlCursor / mlCount
End Function
The last file is this tstListManager.bas
Attribute VB_Name = "tstListManager"
Option Explicit
Sub test()
Dim oListMan As ListManager
Set oListMan = New ListManager
Dim vLists As Variant
vLists = VBA.Array(ThisWorkbook.Sheets("Source").Range("A1:A3").Value2, _
ThisWorkbook.Sheets("Source").Range("B1:B2").Value2, _
ThisWorkbook.Sheets("Source").Range("C1:C5").Value2)
oListMan.Initialise vLists
Dim vSorted As Variant
vSorted = oListMan.WeaveSort
Dim lTotal As Long
ThisWorkbook.Sheets("Dest").Range("A1").Resize(UBound(vSorted, 1)).Value2 = vSorted
End Sub
Finally, the test data was in A1:A3 B1:B2 C1:C5
You should note I have abstracted away any Excel reading/writing logic and the pure weavesort logic is not cluttered.
Feel free to reject outright. Object orientation can be quite controversial and we think differently. :)
I currently have a form in access.
What I want to do is get the value of the last record added.
For example, if i have 10 records, I want to get the value "10", because this is the id of the added last record. I am trying to run a query with the function last id inserted() but it is not working.
This the code I am using :
Dim lastID As Integer
Query = "select last_insert_id()"
lastID = Query
MsgBox (lastID)
What am I missing?
There is a function DMax that will grab the highest number.
Dim lastID As Integer
lastID = DMax("IDField","YourTable")
' or = DMax("IDField","YourTable","WhenField=Value")
MsgBox lastID
The other Domain functions are:
DAvg
DCount
DFirst
DLast
DLookup
DMin
DStDev
DStDevP
DSum
DVar
DVarP
Check with your friendly F1 key for more info
Following on from the last comments, here's a piece of code I used recently to turn the last ID value of a record set into variable for use in VBA. It's not great, however, because I still can't work out how to turn the record's ID field value directly into a variable. Instead I used the inelegant solution of copying the record set into an excel workbook, and then setting the variable value to the value of the cell I just copied into.
EDIT: Worked out how to turn the ID into a simple variable: new code at end
This is all run from a single client workbook:
Option Explicit
Public AftUpD As Long
Public BfrUpD As Long
Sub AssignLstRowAftUpD2()
Dim dbPP As DAO.Database
Dim ResTemp As DAO.Recordset
Dim z As Long
Dim SelectLast As String
SelectLast = "SELECT Max(Table1.ID) AS MaxOfID FROM Table1"
'Debug.Print SelectLast
Set dbPP = OpenDatabase("C:\filepath\Database11.mdb")
Set ResTemp = dbPP.OpenRecordset(SelectLast)
If ResTemp.EOF Then
GoTo EndLoop
End If
Worksheets("Diagnostics").Visible = True
Worksheets("Diagnostics").Range("C4").CopyFromRecordset ResTemp
z = Sheets("Diagnostics").Range("C4").Value
Sheets("Diagnostics").Visible = False
AftUpD = z
'Debug.Print AftUpD
EndLoop:
ResTemp.Close
dbPP.Close
Set dbPP = Nothing
Set ResTemp = Nothing
'Set SelectionLast = Nothing
'z = Nothing
End Sub
Then I used this value as a variable to make a new SQL query:
Sub Query()
'This query uses the highest ID value in a companion spreadsheet (the public
'variable BfrUpD), which is set in a sub I haven't posted here, to find out
'how many records have been added to the database since the last time the
'spreadsheet was updated, and then copies the new records into the workbook
'Be warned: If you run this query when BfrUpD is equal to or greater than AftUpD it
'will cause a crash. In the end user version of this, I use several If tests,
'comparing BfrUpD with other public variables, to make sure that this doesn't
'happen.
Dim WBout As Excel.Workbook, WSout As Excel.Worksheet
Dim dbPP1 As DAO.Database
Dim qryPP1 As DAO.Recordset
Dim ResTemp1 As DAO.Recordset
Dim TestValue As String
Dim strSQL2 As String
TestValue = BfrUpD
'Debug.Print TestValue
strSQL2 = "SELECT * FROM Table1 WHERE (((Table1.ID)>" & TestValue & "))"
'Debug.Print strSQL2
Set dbPP1 = OpenDatabase("C:\filepath\Database11.mdb")
Set qryPP1 = dbPP1.OpenRecordset(strSQL2)
Set WBout = Workbooks.Open("C:\filepath\h.xlsm")
Set WSout = WBout.Sheets("sheet1")
WSout.Range("A1").End(xlDown).Offset(1, 0).CopyFromRecordset qryPP1
qryPP1.Close
dbPP1.Close
WBout.Save
WBout.Close
MsgBox "Data copied. Thank you."
Set WBout = Nothing
Set WSout = Nothing
Set dbPP1 = Nothing
Set qryPP1 = Nothing
Set ResTemp1 = Nothing
End Sub
EDIT: Code for getting field value directly into variable
Dim dbPP As DAO.Database
Dim ResTemp As DAO.Recordset
Dim z As Long
Dim SelectLast As String
SelectLast = "SELECT Max(Table1.ID) AS MaxOfID FROM Table1"
'Debug.Print SelectLast
Set dbPP = OpenDatabase("C:\filepath\Database11.mdb")
Set ResTemp = dbPP.OpenRecordset(SelectLast)
z = ResTemp(0) 'specifying it's array location (I think) - there is only one
'item in this result, so it will always be (0)
AftUpD = z
'Debug.Print AftUpD
ResTemp.Close
dbPP.Close
Set dbPP = Nothing
Set ResTemp = Nothing
'Set SelectionLast = Nothing
'z = Nothing
End Sub
What you would do is set up and save a query that gets the value for you first. Call it MaxID
e.g
SELECT Max(ID) as result FROM Your_Table_Name
Then, in your VBA code, set your variable to that
eg.
Dim IDresult As Integer
IDresult = DLookup("[result]", "MaxID")
MsgBox(IDresult)
I am working on a project that joins two or more pp presentations into one new presentation.
The selection of the original pp presentations is in a webbased Lotus Notes xPage and after the submit, Lotusscript talkes to the OLE Powerpoint object.
Adding the slides into the new Presentation in the right order is no problem.
The problem is that after the adding the original connection with the slides Template(s) is lost.
To solve this I found the next codesnippet:
Sub joiner()
Dim sFileName As String
Dim oDonor As Variant
Dim otarget As Variant
Dim i As Integer
On Error GoTo errhandler
Set otarget = ActivePresentation
Do While sFileName <> ""
Set oDonor = Presentations.Open(Environ("USERPROFILE") & "\Desktop\joiner\" & sFileName, msoFalse)
For i = 1 To oDonor.Slides.Count
oDonor.Slides(i).Copy
With otarget.Slides.Paste(otarget.Slides.Count + 1)
.Design = oDonor.Slides(i).Design
.ColorScheme = oDonor.Slides(i).ColorScheme
End With
Next i
oDonor.Close
Set oDonor = Nothing
sFileName = Dir()
Loop
End Sub
I have to declare the presentations oDonor and oTarget as a Variant because lotusscript doesn't understand Dim oTarget As Presentation
This is probably the reason why the code returns a typemismatch error at:
.Design = oDonor.Slides(i).Design
My questions are:
Am I doing the join the right way or is there a better solution?
Is there a solution for the typemismatch error?
*ps: The result presentation doesn't have to be editable, so maybe it is not necessary to add templates.
Update 04-10-2012:
The next code solves the template problem.
What still is missing now is the background image used by some slides.
See: https://stackoverflow.com/questions/12731691/how-to-export-a-backgroundimage-of-a-slide-to-the-filesystem
Dim oDonor As Variant
Dim h As Integer
Dim thetmplt As Variant
Dim thetmpltname As String
Dim thetmpltnew As Variant
Dim thetmpltnamenew As String
Set oDonor = PPApplication.Presentations.Open(tempdirectory +
jobid+CStr(filenamearray (i)),False,False,False)
thetmplt = oDonor.TemplateName
Call oDonor.SaveAs(tempdirectory +jobid+CStr(i)+ thetmplt+".pot" ,5, -1)
For h = 1 To oDonor.Slides.Count
Dim oTargetSlide As Variant
oDonor.Slides(h).Copy
Set oTargetSlide = newPres.Slides.Paste()
Next
Dim theubound As Variant
theubound = oDonor.Slides.Count
ReDim thearray(1 To k + theubound) As Variant
For k = k To k + oDonor.Slides.Count-1
thearray(k) = k
Next
Call newPres.Slides.Range(thearray()).ApplyTemplate(tempdirectory +
jobid+CStr(i+thetmplt+".pot")
oDonor.Close
Set oDonor = Nothing
This is just a hunch, but try:
Dim oTargetSlide as Variant
Set oTargetSlide = otarget.Slides.Paste(otarget.Slides.Count + 1)(1)
With oTargetSlide
.Design = oDonor.Slides(i).Design
.ColorScheme = oDonor.Slides(i).ColorScheme
End With
I've been trying to resurrect an existing drawing check macro, and want to find the co-ordinates of any note blocks on each sheet. I've been modifying code found here using the GetAttachPos method from this page, but for some reason any co-ordinates returned come back around (8.80942311664557E-03,2.24429295226372E-03).
I'm thinking that the problem is that I've missed a reference somewhere, but I'm not sure where. Although it's definitely finding the notes since it passes back their text. Anyway, here's the method I'm testing at the moment:
Sub Main()
Dim swApp As SldWorks.SldWorks
Set swApp = CreateObject("SldWorks.Application")
Dim NoteNumbersText As String
Dim NoteText As String
Dim NumberofSheets As Integer ' The number of sheets in this drawing
Dim NamesOfSheets As Variant ' Names of all of the sheets
Dim sheet As SldWorks.sheet ' The Sheet that we are working on
Dim LocalView As SldWorks.View ' Current View that we are looking at
Dim LocalNote As SldWorks.Note ' Current Note that we are looking at
Dim TextFormat As SldWorks.TextFormat ' Current text format object of a note
Dim Xpos As Double ' X, Y Z position on the drawing in Metres
Dim Ypos As Double
Dim SizeOfSheet As Double
Dim x As Integer ' general Loop Variables
Dim j As Integer
Dim k As Integer
Dim l As Integer
Dim vPosition As Variant
Dim vNote As Variant ' Single note
Dim swNote As SldWorks.Note ' Single Solidworks Note Object
Dim ThisAnnotation As SldWorks.Annotation
Dim swBlockInst As SldWorks.SketchBlockInstance
Dim swBlockDef As SldWorks.SketchBlockDefinition
Dim NumofNotes As Integer
Dim ArrayOfNotes() As NoteInfo
Dim LocalDrawingDoc As SldWorks.DrawingDoc ' Declared as an Object so that non Drawings can be detected!
Dim LocalPart As SldWorks.ModelDoc2 ' Declared as an Object so that non Drawings can be detected!
Dim strShtProp As Variant
Set LocalDrawingDoc = swApp.ActiveDoc
Set LocalPart = swApp.ActiveDoc
ReDim ArrayOfNotes(0)
' Get the sheet names and the number of them
NamesOfSheets = LocalDrawingDoc.GetSheetNames()
NumberofSheets = LocalDrawingDoc.GetSheetCount
' store this sheet name
Set sheet = LocalDrawingDoc.GetCurrentSheet()
strShtProp = sheet.GetProperties() ' get the sheet properties use much later for converting position into ref
SizeOfSheet = strShtProp(5)
Dim SwSketchMgr As SldWorks.SketchManager
Set SwSketchMgr = LocalDrawingDoc.SketchManager
Dim i As Integer
Dim vBlockDef As Variant
Dim vBlockInst As Variant
Dim strReturn As String
' Dim bret As Boolean
vBlockDef = SwSketchMgr.GetSketchBlockDefinitions
For x = NumberofSheets - 1 To 0 Step -1
If LocalDrawingDoc.GetCurrentSheet.GetName <> NamesOfSheets(x) Then LocalDrawingDoc.ActivateSheet NamesOfSheets(x)
Set LocalView = LocalDrawingDoc.GetFirstView
While Not LocalView Is Nothing
If Not IsEmpty(vBlockDef) Then
For i = 0 To UBound(vBlockDef)
Set swBlockDef = vBlockDef(i)
vBlockInst = swBlockDef.GetInstances
vNote = swBlockDef.GetNotes
If Not IsEmpty(vNote) Then
For j = 0 To UBound(vNote)
Set swNote = vNote(j)
NoteNumbersText = Trim(swNote.GetText)
If Left(NoteNumbersText, 1) = "n" And Len(NoteNumbersText) > 1 And Len(NoteNumbersText) < 4 Then
Set ThisAnnotation = swNote.GetAnnotation
'vPosition = swNote.GetAttachPos
vPosition = ThisAnnotation.GetPosition
Xpos = vPosition(0)
Ypos = vPosition(1)
Debug.Print ("Note " & NoteNumbersText & ": " & Xpos & "," & Ypos)
End If
Next j
End If
Next i
End If
Set LocalView = LocalView.GetNextView
Wend
Next x
End Sub
Turns out that SolidWorks is set up to return positions of blocks relative to the drawing view on which they're placed. Calling GetXForm for the view which they are placed on then provides a way of calculating the absolute position of each note.