VBA editing bookmark in a building block [duplicate] - ms-access

I want to create a template in Word, that I can then use Access to merge data into. The Access data has various levels of grouping. Within each grouping, there are subqueries that may also have grouping. Because there is grouping, certain pages will need to be repeated.
Example: I have a query that prints the details of a classroom. There are many students in each classroom, as well. So, I would like to have a DOTM template that groups each student by class. Then prints the first class details, then the students, then moving on the next class, then students, etc.
The Access tables/queries, etc. are not an issue. It's creating a template, then merging it, that I'm having issues with. Right now, I just have a simple template (Dotm file). The file has some boilerplate stuff, and some bookmarks. I then use this code to interact with the dotm file:
Dim objWord As Word.Application
Dim PauseTime, Start, Timer As Integer
Dim wrkCurrent As DAO.Workspace
Set objWord = CreateObject("Word.Application")
objWord.Visible = False 'True is visible
Dim sql As String
sql = "SELECT * FROM tbl_School" 'ex query that produces more that one record
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Set wrkCurrent = DBEngine.Workspaces(0)
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset(sql)
objWord.Documents.Add ("C:\test\Test.dotm")
'this template has one page, with one bookmark, School_Name. What I want
' to happen is that, for every record, create a new page, with this
'bookmark filled in.
If (Not rst.EOF) Then
With rst
Do Until .EOF
objWord.ActiveDocument.Bookmarks("School_Name").Select
objWord.Selection.Text = rst!school_name
.MoveNext
Loop
End With
End If
objWord.ActiveDocument.SaveAs ("C:\test\MyNewDocument.docx")
objWord.PrintOut
objWord.Quit
Set objWord = Nothing
The problem is that this only prints the first one, then errors. How would I do the grouping? FYI, I know I can do this in reports, but I must allow the report to be exported to Word, retaining images, graphics, etc. which the export loses.

Related

How to access updated DAO recordset properties during a transaction?

I am developing an application in Access which uses transaction processing in DAO. I use a class to represent a body of data to be imported from another source. Among other things, this class houses a dictionary of recordsets, each of which has a new record added or an existing record edited depending on whether another record exists with the same primary key or not. I create an instance of this class from another procedure in a normal code module and use the properties and methods of the class to perform analysis and finalize the data extract.
In one procedure I want to subsequently edit the most recently added or edited record for each recordset in order to augument what has already been done to those records via the class' methods. The problem is that after a transaction has begun processing the .Recordcount property doesn't appear to update until the transaction has been committed, and it doesn't look like I can set a bookmark to the newly-added/edited record prior to committing it. The latter issue throws a "No Current Record" error when trying to set a bookmark to a record that has just been added (the Recordcount property is still at 0).
Am I correct in the understanding that DAO bookmarks are not a viable way of recalling a record that may have been added prior to committing a transaction? Is there another better way of doing this, or does anyone have any other suggestions?
If you need to reference a record but are unable to via bookmark, hang onto the data by using a static array of type variant. Use one of the elements in the array to act as a bookmark of sorts. To process the array to find your bookmark, keep a static variable with the index value of the bookmark in the array.
Hope that helps some.
It turns out that although the .Bookmark property was not available during the transaction the .LastModified property was, which is a bookmark pointer to the record I wanted to access anyway. I simply went from:
Private Sub Test()
dim vBookmark as Variant
dim wsp as Workspace
dim rs as DAO.Recordset
dim rs2 as DAO.Recordset
set wsp = DBEngine.Workspaces(0)
set rs = CurrentDB.OpenRecordset("TableName", dbOpenTable)
wsp.BeginTrans
'... Make some record active
vBookmark = rs.Bookmark
'Note: ^ Error is thrown here when recordset has 0 records
'... Later
set rs2.Bookmark = vBookmark
wsp.CommitTrans
End Sub
To this:
Private Sub Test()
dim vBookmark as Variant
dim wsp as Workspace
dim rs as DAO.Recordset
dim rs2 as DAO.Recordset
set wsp = DBEngine.Workspaces(0)
set rs = CurrentDB.OpenRecordset("TableName", dbOpenTable)
wsp.BeginTrans
'... Make some record active
vBookmark = rs.LastModified
'Note: ^ Correctly stores bookmark even during an uncommitted transaction
'... Later
set rs2.Bookmark = vBookmark
wsp.CommitTrans
End Sub

Store records in an instance of a class to make available anywhere in project throughout the life of the program

I have a form with a combo box that I populate with records from a access db (I use linked tables). What I'd like to do is store the records in an instance of a class throughout the life of the program rather than querying the table each time the user selects the combo box. Right now I'm calling a funtion from a private sub, the function returns the DAO recordset data which is what I want but the question I have is, how do I pass the data from the function recordset to a public DAO.Recordset, assuming if this was possible it would hold the recordset data throughout the life of the running program. When I run the code and it finishes Public Rs() in the Watch output window says; Recordset(0 to -1) and No Variables, I'm not sure how to correct this.
Here's my code:
Private Sub cmdGetRecordset_Click()
Dim strDescription As String
Dim strModel As String
Dim rst As DAO.Recordset
Set rst = getdevices()
Do While Not rst.EOF
strDescripton = rst!DESC
strModel = rst!MODEL
Debug.Print strDescription & " " & strModel
rst.MoveNext
Loop
'rst.Close'
'Set rst = Nothing'
End Sub
''''''
Option Compare Database
Public Rs() As DAO.Recordset
Function getdevices() As DAO.Recordset
Dim Rs As Object
Dim CurDatabase As Object
connectDatabase
Set CurDatabase = CurrentDb
Set Rs = CurDatabase.OpenRecordset("SELECT * FROM tblCDA")
Set getdevices = Rs
closeDatabase
End Function
I don't fully understand your goal to be honest. Depending what you're trying to do there are a number of ways to tackle the issue.
1) Are you just pre-populating controls when the form loads?
Either run a series of queries in the Form_Load event each time the form is loaded and pass the results to each combobox as it's source OR create a temporary table with the values when the database is opened and query the temp table.
2) Are you trying to capture a persistent state of what the user selected when moving between forms or reports?
Use a temp table or dynamic array to capture the user selections (values of the comboboxes) and query the table or iterate through the array when referencing a value somewhere else.
3)Are you trying to dynamically change combobox values based on user selections?
Sadly I think having to requery the source data is the only option here. You could get all of the data from various tables and store it in memory using arrays and then iterate through the arrays as needed to change the combobox values but that would be a substantial memory footprint depending on the data.

Converting from Access to SQL Back End (Multi-Value Fields)

So I'm converting an access back-end to SQL. I've tried a few different tools (SSMA, Upsizing Wizard, and a simple import). I've found so far that the SSMA tool and importing seem to work the best, eliminating most of the work necessary for me. However, I'm running into one issue I can't figure out how to overcome.
Two fields allow multiple values (dropdown with check boxes). In converting these, it errors in a way that it not only doesn't carry all of the information over, but also grabs information from another field (and doesn't carry that information over).
I've tried forcing access to only accept the first value (and get rid of multi-values all together), but it won't let me.
Any ideas?
This should get you started. It will turn all those values which are selected in the multi select field into their own table. You will need to establish the relationships between the three tables to create a true many to many relationship after the fact.
Sub ExtractMultiValueFields()
Dim JoinTable As New DAO.TableDef
JoinTable.Name = "JoinTable"
With JoinTable
.Fields.Append .CreateField("MainTableId", dbInteger)
.Fields.Append .CreateField("JoinToValue", dbText)
End With
Dim joinRs As DAO.Recordset
CurrentDb.TableDefs.Append JoinTable
Set joinRs = CurrentDb.OpenRecordset("JoinTable")
Dim rs As DAO.Recordset
Dim childrs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("select * from table1")
Do While Not rs.EOF
Debug.Print rs("ID")
Set childrs = rs("col1").Value
Do While Not childrs.EOF
Debug.Print childrs("value") 'always "value"
joinRs.AddNew
joinRs("MainTableId") = rs("ID")
joinRs("JoinToValue") = childrs("value")
joinRs.Update
childrs.MoveNext
Loop
rs.MoveNext
Loop
End Sub

Why does a datasheet view show only 100 records when based on sorted and filtered adodb recordset

I have an access 2003 front end database with a form that allows users to see a sorted and/or filtered view of some data. The data is displayed in a sub-form.
The base data (loaded when the form is opened) is retrieved into a disconnected ADODB.Recordset object (static client side cursor). The sub-form's Recordset property is set to the disconnected recordset and all records are displayed.
Applying just a sort (in code) to the recordset object and then setting the sub-form to use the sorted recordset displays the data with the correct sort applied. The filter property is set to adFilterNone for this to work. All records are displayed (correct).
Applying just a filter (in code) to the recordset object and then setting the sub-form to use the filtered recordset displays the data with the correct filter applied. The sort property is set to an empty string for this to work. All records matching the filter are displayed (correct).
When both the sort property AND the filter property are set on the recordset, and that recordset is then set to the sub-form's Recordset property, only the first 100 matching records are displayed (incorrect). They are displayed in sort order. The underlying recordset object shows the correct record count for the filtered records, they just don't all display on the form.
Does anyone know why this is happening and if there is a way to get around this apart from creating a recordset using a new SQL string each time?
Thanks in advance.
What you are seeing with filtering and sorting is a known limitation of ADO recordsets.
Take a look at the list of ADO Cons listed on this page. Notice the one on the bottom:
http://www.utteraccess.com/wiki/index.php/Choosing_between_DAO_and_ADO
I couldn't find any documentation on MS's Support site about this so I don't know if it's a bug or simply a limitation. I'm assuming it's the latter.
FYI, I think MS has basically forgotten about ADO (classic). The last release of MDAC (which is how you obtain ADO) was 5/10/2005.
As far as a work-around for this problem, you can try using this function. It returns a new, filtered and sorted recordset. Just keep a big, full recordset handy and use this function to get a new one every time you do a sort/filter. This does increase your overall resource usage, especially memory.
I have used this function but it hasn't been fully tested to make sure it's bullet proof in every way. You might quickly find some bug or limitation with it. I actually had a note that it needed some kind of work but my note was unclear, I didn't have time to test it now, and I did find that I'm using this function in my production code so I think it's working.
Public Function GetFilteredRecordset(ByRef rsSource As ADODb.Recordset, _
ByVal sWhere As String, _
Optional ByVal sOrderBy As String, _
Optional ByVal LockType As ADODb.LockTypeEnum = adLockUnspecified) As ADODb.Recordset
Dim sOriginalOrderBy As String
sOriginalOrderBy = rsSource.Sort
Dim F As ADODb.Field
For Each F In rsSource.Fields
'Debug.Print F.Name
Next F
rsSource.Filter = sWhere
If sOrderBy <> "" Then
If Left(LCase(sOrderBy), 8) = "order by" Then sOrderBy = Trim(Right(sOrderBy, Len(sOrderBy) - 8))
rsSource.Sort = sOrderBy
End If
Dim rsF As ADODb.Recordset
Dim objStream As ADODb.Stream
'Create a New ADO 2.5 Stream object
Set objStream = New ADODb.Stream
'Save the Recordset to the Stream object in XML format
rsSource.Save objStream, adPersistXML
'Create an exact copy of the saved Recordset from the Stream Object
Set rsF = New ADODb.Recordset
rsF.Open objStream, , , LockType
rsSource.Filter = ""
rsSource.Sort = sOriginalOrderBy
'Close and de-reference the Stream object
objStream.Close
Set objStream = Nothing
Set GetFilteredRecordset = rsF
End Function
Another strange limitation of filtering ADO recordsets is that your OR keyword must always be on the top level. That is also documented in the link I posted above although I'm not sure if the examples given are accurate.

Exporting data from MS Access to Excel using VBA

I have a table in MS Access, which has the following data to be exported to excel
Release numbers
Test cases
Results
After exporting to Excel I want to have distinct release numbers as rows starting from A2 and distinct test case name as columns starting from B1. There might be couple thousands records. Then each cell will be set to result tag. Additionally will need some fancy coloring/bordering stuff.
The question - is it possible to do this using VBA in Access and if yes what is the way to go? Any hint, sample, example, resource would be appreciated... I've googled but the most thing I came accross is DoCmd.TransferSpreadsheet or DoCmd.OutputTo which I believe will not do what I want. Saw some examples with CreateObject("Excel.Application") but not sure what are limitations and performance using this way.
I don't know if it would work for your case, but you might try adding the VBA code to an Excel document rather than the Access database. Then you could refresh the data from the Excel file and add the formatting there much easier. Here is one example:
http://www.exceltip.com/st/Import_data_from_Access_to_Excel_%28ADO%29_using_VBA_in_Microsoft_Excel/427.html
(Or see other examples at http://www.exceltip.com/exceltips.php?view=category&ID=213)
Again, it may not work for your case, but it may be an option to consider. Essentially, instead of pushing from Access, you would pull from Excel.
Yes, there are many cases when the DoCmd.TransferSpreadsheet command is inadaquate.
The easiest way is to reference the Excel xx.x Object model within Access (Early Binding). Create and test your vba export function that way. Then once you are satisfied with your output, remove the Excel object model reference, then change your objects to use use Late Binding using CreateObject. This allows you to easily have other machines that are using different versions of Excel/Access to use it just the same.
Here is a quick example:
Sub ExportRecordsetToExcel(outputPath As String, rs As ADODB.Recordset)
'exports the past due report in correct formattig to the specified path
On Error GoTo handler:
Const xlUP As Long = -4162 'excel constants if used need to be referenced manually!
Dim oExcel As Object
Dim oBook As Object
Dim oSheet As Object
Dim row As Long
If rs.BOF And rs.EOF Then
Exit Sub 'no data to write
Else
rs.MoveFirst
End If
row = 1
Set oExcel = CreateObject("Excel.Application")
oExcel.Visible = False 'toggle for debugging
Set oBook = oExcel.Workbooks.Add 'default workbook has 3 sheets
'Add data to cells of the first worksheet in the new workbook.
Set oSheet = oBook.worksheets(1)
Do While rs.EOF = False
oSheet.range("A" & row).value = rs.Fields("MyField").value
'increase row
row = row + 1
Loop
oBook.SaveAs (outputPath)
'tidy up, dont leave open excel process
Set oSheet = Nothing
Set oBook = Nothing
oExcel.Quit
Set oExcel = Nothing
Exit Sub
handler:
'clean up all objects to not leave hanging processes
End Sub