I was initially very pleased to discover the attachment field in Access 2010. It's a feature that aesthetically irks my inner database purist but my inner lazy sod is in charge here and it does look, on the face of it, like it could make one of my current projects much easier/simpler. Happily it displays pictures/icons automatically on the forms and reports but (why is there always a but eh!) it only displays the first one and I need it to display all of them.
You can of course scroll through the attachments one at a time but I'm pretty sure my client won't wear that, despite his request that I complete the project in MS-Access which, seemingly, only has very rudimentary built in options for display :/ BUT...
I may well be wrong, I've got almost no MS-Access experience. My coding background is firmly LAMP stack and web so I'm deeply ignorant of what's on offer in the Windows/Access ecosystem. I suspect there are excellent 3rd party reporting tools that give very flexible layout but I need to see all the attachments on the form, not just the reports.
So, blundering blindly into the void my initial strategy is this...
Create a separate table for attachments where each field is an "attachment" containing a single item only. Then use scripting in the forms and reports to...
Query that table for all attachments belonging to the record in question
Display/Format those fields as some sort of list
Dynamically append a fresh attachment field to the end of that list so the user has somewhere to upload a next attachment
Make the form page refresh whenever an attachment is added so there's aways a free one.
So, my questions are...
Is what I describe feasible in Access?
Am I missing a much simpler / better / canonical solution?
How powerful is Access's scripting language(s) with reference to display? i.e clunky or pixel perfect?
It's not still Visual Basic is it? (noooooo! ;)
If so are there any other scripting languages I can use within forms/reports?
Sorry, I know it's a bit of a long wooly question but I'm a fish out of water here!
Thanks,
Roger
Let us say I have a table with an attachment:
Let us say that I have three images in one of those attachment fields that I wish to display. I can create a query:
After which I can create a continuous form:
I searched this for a similar problem. I have multiple attachments per field. I intend to use the field to store a .jpg and a .pdf for two image controls which I created by dragging the field from the properties to the form. I have named them ctlImage and ctlFeatures. I am using this form as a non modal popup from a more info button on the product catalog form. So far I am able to search records in the product catalog and use the selected listbox search result to open the form with a where clause to set the details form to the current record. When I try to manipulate the ctlImage looking for image type the only property which shows in intellisense is ctlImage.value. I was hoping to assign ctlImage to jpg and ctlFeatures to pdf Here is the frmProducts btnMoreInfo_Click code and the frmCatalogDetails code in case one is messing up the other.
Private Sub btnMoreInfo_Click()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim str As String
Dim row As Long
Set db = CurrentDb
Set rs = db.OpenRecordset("tblProducts", dbOpenSnapshot)
If Me.lstSearchResults.ItemsSelected.Count > 0 Then
' Debug.Print Me.lstSearchResults.Column(0)
row = Me.lstSearchResults.Column(0)
rs.MoveFirst
rs.FindFirst "ProductID=" & row
Debug.Print rs("Description")
DoCmd.OpenForm "frmCatalogDetails", acNormal, , "ProductID=" & Me.lstSearchResults.Column(0, Me.lstSearchResults.ItemsSelected), acFormReadOnly, acWindowNormal
Else
MsgBox "You must select a product"
Exit Sub
End If
End Sub
and Catalog details
Option Compare Database
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim fld As DAO.Field
Private Sub Form_Load()
Set db = CurrentDb
'Set rs = db.OpenRecordset("tblProducts", dbOpenSnapshot)
'Set fld = rs("image")
Debug.Print rs("ProductID")
Debug.Print rs("Description")
'me.ctlImage.ControlSource =
'Me.ctlImage.CurrentAttachment = fld.Value
End Sub
The rs statements are commented out because I am having trouble with this form recognizing the rs from the parent form. I am getting a with block variable not set, but if I do rs.openRecordSet then the recordset goes back to the first row. Aside from your answer above, I have seen very little about manipulating the attachment object and the help has been difficult as it doesn't even cover accessing inside the attachment field. I am at a point where I will do more asking than answering on this topic, and I very much appreciate the time many of you take to craft an answer.
Rollin
Motto: Ask for help when needed, give help when asked, and remember where you came from.
Related
Okay, friends, I'm leaving my job in a week and a half, and I'm trying to make what I've done easier for my boss to do. He has no access knowledge, so I'm trying to create a form that will automate the reports I've been generating. Rather than create a different form for all the different reports, I'm trying to automate it from a table of parameters. Here's what I'm going for:
I have a table, which I have created, which is comprised of 5 fields. I'd like to use these fields to fill parameter fields in a standard form template. The five fields in my table are as follows:
The type of query being run (the result spit out)
The queries that generate this report, separated by a comma and no space. "QRYNAMEA,QRYNAMEB"
The Table which these queries generate, which will be used by transferspreadsheet
The destination excel file, which already has a pivot table set up to feed of the data.
The input sheet of this excel file. Currently, all of these sheets are called "Input". (that isn't important)
My issue comes with having no idea where to go after I've made my combo box. I know enough visual basic to automate my queries, but not enough to populate the form with the information in 3,4 and 5 (so far, I've been manually changing these for different queries). I have no idea how to look up the record in the table from the choice in the 'choosebox', and then select individual fields from that in my automation.
I'm pretty confident in my ability to parse #2 and automate the queries, and to put the values into the fields I'm looking at, but I don't know how to actually pull those values from the table, before I can do these things. I also can't seem to describe this well enough for google to help me.
Has anyone done something like this before? I'm assuming I just lack knowledge of one of the VBA libraries, but I've not had any luck finding out which.
edit:
my inclination at this point is to create a query for this table, which will return a single field depending on the input I give. I can imagine doing this in SQL, but I still don't know how to populate the forms, nor extract the field object from the table once I get it.
I have to head out for the day, but I'll be back on Friday to keep working on this, and I'll post my solution, once I find it. This seems like a unique conundrum, and it would be nice to give an answer to it.
Final edit: code is polished (does not have much in the way of error handling):
The first method, which pulls the fields from the table and populates the form, is activated by choosing a new entry in the combo box and looks like this:
Private Sub QuerySelect_Change()
Dim db As Database
Dim rec As Recordset
Set db = CurrentDb
Set rec = db.OpenRecordset("SELECT [Queries to Run], [Source Table], [Destination Spreadsheet], [Destination Sheet Name] FROM TBL_QRY_SETTINGS WHERE TBL_QRY_SETTINGS.[Query Type] Like '" & [Forms]![QuerySelector]![QuerySelect] & "';")
[Forms]![QuerySelector]![QueriesToRun].Value = rec("Queries to Run")
[Forms]![QuerySelector]![SourceTable].Value = rec("Source Table")
[Forms]![QuerySelector]![FileDest].Value = rec("Destination Spreadsheet")
[Forms]![QuerySelector]![SheetName].Value = rec("Destination Sheet Name")
Set rec = Nothing
Set db = Nothing
End Sub
The second code pulls that data to run the query. I like how this turned out. It runs when a button near the combobox is clicked.
Private Sub DynamicQuery_Click()
Dim qryArray As Variant
Dim i As Integer
qryArray = Split([Forms]![QuerySelector]![QueriesToRun], ",")
DoCmd.SetWarnings False
For i = LBound(qryArray) To UBound(qryArray)
Debug.Print qryArray(i)
DoCmd.OpenQuery (qryArray(i))
Next
DoCmd.SetWarnings True
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, [Forms]![QuerySelector]![SourceTable], _
[Forms]![QuerySelector]![FileDest], _
True, [Forms]![QuerySelector]![SheetName]
End Sub
Note that the final code for part (1) is almost the same as the selected answer, except that I am grabbing more than one field. This works because I know that I have unique "Query Types", and my recordset will only contain one record.
Anyway, I hope some people stumble upon this and find it useful. Send me a message if you do. As far as I can tell from brief googling, this sort of automation work has not been done in access. It should make it easier for access-illiterate to run their own queries, and be simple for designers to add to, if they want all their queries available after a few clicks.
Someone could conceivably use this to automate a variety of reports in sequence, by iterating through a table like the one I reference.
I may be massively misunderstanding what you're doing, but I think it's as easy as creating a new form using the form wizard. It will let you choose the table that contains the data, and it will let you choose which fields you want to add.
You can later change any of the textboxes to combo boxes which will allow you to limit the choices available to fill in.
Am I understanding that correctly?
EDIT: This will fill a variable (MyRandomField) with the contents of a field in a table
Dim db as Database
Dim rec as Recordset
set db = CurrentDB
set rec = db.OpenRecordSet("Select SomeField from SomeTable Where Something = 'SomethingElse'")
MyRandomField = rec("SomeFieldName")
set rec = Nothing
set db = Nothing
How do I refresh all via vba?
I want to refresh all open forms...
The reason #CodeSlave's answer probably didn't do what you needed, is that the VBA method you need is requery, not refresh. Refresh will display changes made to existing records, but only requery will display newly added records.
Here is a more concise version of the code (put this in a Module, so that you can call it from any form):
Public Sub RequeryOpenForms()
Dim f as Form
For Each f In Access.Forms
f.Requery
Next
End Sub
NOTE: Unfortunately, requery has the side-effect of losing the currently selected record. This can be particularly frustrating for a use if there are a long list of records, since they may have to scroll down a long way to find the record that they were previously looking at.
What about something like this?
Sub AllForms()
Dim obj As AccessObject
dim dbs As Object
Dim i As Integer
dim intFormCount as Integer
Set dbs = Application.CurrentProject
intFormCount = dbs.AllForms.Count - 1
For i = 0 To intFormCount
If dbs.AllForms(i).isloaded = True Then
dbs.AllForms(i).refresh
End If
Next
End Sub
In a comment above you say:
I'd like the new record just added to
the table to be available in that form
when I go back to it
You might want to examine the Activate event. But it bothers me to requery the form unless you know for a fact that records have been added. I expect that if I needed to do this (I've never done so, actually -- my users know about Shift-F9 where it's relevant, but most of them never need it), I'd use the OnActivate event and check the current count of records and only requery the form if the count doesn't match the current recordset.
But again, I consider it a fairly exotic thing to do. In general, it's probably an architectural error to have too many forms sitting around with an open recordset that you depart from and then come back to. They should probably be entirely closed instead of being left open when you're away from them. This reduces the number of table handles, as well as the number of locks held on the back end database, both of which can be problems if not properly managed.
So I recently have been trying to incorporate more sub forms to make the UI more friendly. So I have a pre developed form that runs some VB, so for examples sake lets just say that it runs SQL statement, returns the recordset, and fills text boxes with the data (because this is sort of an interactive dashboard concept:
dim db as database
dim rs as recordset
dim sql as string
set db = current db
sql = "SELECT * FROM tblMain;"
set rs = db.OpenRecordSet(sql)
rs.movefirst
[forms]![DashboardSubName].txtValue1 = rs!Value1
rs.close
set rs = nothing
set db = nothing
with error handling that returns a error "DashboardSubName" not found. So orginally this form was its own form, and opening it by itself it works fine, but once I use it within the parent form i get this error.
im sure it is simply something i am not aware of, but what gives?
thanks guys
justin
When a Form is loaded as a sub-form, it is referenced as if it were just another control on the Main Form - it is no longer part of the global Forms collection.
So the correct reference should be:
Me!DashboardSubName.Form.txtValue1
where DashboardSubName is the name of your sub-form control.
Assuming the code you showed us is part of the subform, rather than the parent form, replace
[forms]![DashboardSubName].txtValue1 = rs!Value1
with
Me.txtValue1 = rs!Value1
The reason for the syntax here is that the subform control is distinct from the subform embedded inside it. The subform control has properties of its own (limited in comparison to the subform itself), and to get to the properties/methods of the subform embedded in it you have to specify that you want the form that the subform embeds.
Me!MySubformControl
...is the subform control.
Me!MySubformControl.Form
...is the form embedded in the subform control.
Last of all, I really wonder why you're walking a recordset to update data in a subform. This is not normal practice, though it's tangential to your actual question.
At work we have this reporting tool. It is distributed to everyone as a MDE tool that is locked up pretty good (VBA is unviewable, cannot import any of the table, query, forms, etc). When each person fills out their applicable portion there is a button that conslidates it into a text file, and then sends it to Outlook. Then everyone emails it to one person.
So I have come up with need to use this in my own database:
Remote Employees fill out a form that creates a power point presentation for them, and this part I think I have nailed down. This helps us track metrics on these presentations, rather than the employee manually creating a the ppt, and then we coming behind and manully entering the data from the brief into a form. Makes sense right.
Here is my problem, at the office, this is solved, but for those out in the field I need a similiar tool like the one mentioned above; where they get the benefit of the autogenerated ppt, and then I can have them send me the text file through email I can add to the db.
Here are my questions because I am just getting into the beginning of this:
-The form is pretty long because there is A LOT of info going into a ppt, so I use one form with tabs for different sections, but it all becomes on record in the table, and one ppt. How do I turn all this information, this one record, into a text file, and how do I use the Send to Outlook, all with one button click??
-When the user emails em the text file, how do I import it into the database table?
-How do you lock up a MDE so that the VB is unviewable, and the object cannot be imported into another application?
any other advice, tips, "your crazy man!"s, are welcome! thanks as always!
Have you considered replication rather that a text file? The data would be stored in a replicated back-end file with Access Security, which could be returned to you. CDO should suit for emailing.
Text
Access has DoCmd.TransferText, which will allow you to both export and import a text file.
CDO
Private Sub SendEmailCDO()
'Requires reference to Microsoft CDO for Windows 2000
Dim cdoConfig As Object
Dim strSubject As String
Dim strBody As String
Dim strFile As String
Dim cdoMessage As Object
'Set up detail of the mail server
Set cdoConfig = CreateObject("CDO.Configuration")
With cdoConfig.Fields
.Item(cdoSendUsingMethod) = 2 ''cdoSendUsingPort
.Item(cdoSMTPServerPort) = 25
.Item(cdoSMTPServer) = "smpt.themailserver.com"
.Item(cdoSendUserName) = "abc#themailserver.com"
.Item(cdoSendPassword) = "password"
.Update
End With
''This is the subject line for the email.
strSubject = "Membership List"
''This is the message with a little HTML.
strBody = "<P>Here is the membership list for <FONT color=#ff0000>" _
& Format(Date, "mmmm yyyy") & "</FONT>.</P><P>Regards, LTD</P>"
''Location of Attachment
strFile = "C:\Docs\MembershipList.rtf"
''Set up the email message
Set cdoMessage = CreateObject("CDO.Message")
With cdoMessage
.Configuration = cdoConfig
.Subject = strSubject
.From = "me#here.com"
.To = "someone#there.com"
.HTMLBody = strBody
.AddAttachment strFile
.Send
End With
End Sub
Further information: http://wiki.lessthandot.com/index.php/Access_and_Email
#Justin asks:
-How do you lock up a MDE so that the VB is unviewable, and the object
cannot be imported into another
application?
The question makes no sense, unless the person asking it has failed to grasp what an MDE is. THERE IS NO VIEWABLE CODE LEFT IN AN MDE. It has been stripped out and all that remains is the compiled p-code. For an a helpful article on VBA compilation in Access that incidentally explains the relationship between canonical code and compiled p-code, see Michael Kaplan's "The real deal on the /Decompile switch."
Keep in mind that this applies only to code-bearing objects (forms/reports/modules) and not to tables and queries.
There is some literature available at expert's exchange and at teck republic about using the combobox.recordset property to populate a combobox in an Access form.
These controls are usually populated with a "SELECT *" string in the 'rowsource' properties of the control, referencing a table or query available on the client's side of the app. When I need to display server's side data in a combobox, I create a temporary local table and import requested records. This is time consuming, specially with large tables.
Being able to use a recordset to populate a combobox control would allow the user to directly display data from the server's side.
Inspired by the 2 previous examples, I wrote some code as follow:
Dim rsPersonne as ADODB.recordset
Set rsPersonne = New ADODB.Recordset
Set rsPersonne.ActiveConnection = connexionActive
rsPersonne.CursorType = adOpenDynamic
rsPersonne.LockType = adLockPessimistic
rsPersonne.CursorLocation = adUseClient
rsPersonne.Open "SELECT id_Personne, nomPersonne FROM Tbl_Personne"
fc().Controls("id_Personne").Recordset = rsPersonne
Where:
connexionActive: is my permanent ADO connection to my database server
fc(): is my current/active form
controls("id_Personne"): is the
combobox control to populate with
company's staff list
Access version in 2003
Unfortunately, it doesn't work!
In debug mode, I am able to check that the recordset is properly created, with requested columns and data, and properly associated to the combobox control. Unfortunately, when I display the form, I keep getting an empty combobox, with no records in it! Any help is highly appreciated.
EDIT:
This recordset property is indeed available for the specific combobox object, not for the standard control object, and I was very surprised to discover it a few days ago.
I have already tried to use combobox's callback function, or to populate a list with the "addItem" method of the combobox,. All of these are time consuming.
To set a control that accepts a rowsource to a recordset you do the following:
Set recordset = currentDb.OpenRecordset("SELECT * FROM TABLE", dbOpenSnapshot)
Set control.recordset = recordset
Works with DAO Recordsets for sure, I haven't tried ADO recordsets because I don't have any real reason to use them.
When done this way, a simple requery will not work to refresh the data, you must do a repeat of the set statement.
As was said, you have to get the RowSourceType to "Table/Query" (or "Table/RequĂȘte" if in french) in order to show query results in the combobox.
Your memory problems arise from opening the recordset (rsPersonne) without closing it. You should close them when closing/unloading the form (but then again you would have scope problems since the recordset is declared in the function and not in the form).
You could also try to create and save a query with Access's built-in query creator and plug that same query in the RowSource of your combobox. That way the query is validated and compiled within Access.
I found the trick ... the "rowSourceType" property of the combobox control has to be set to "Table/Query". Display is now ok, but I have now another issue with memory. Since I use these ADO recordsets on my forms, memory usage of Access is increasing each time I browse a form. Memory is not freed either by stopping the browsing or closing the form, making MS Access unstable and regularly freezing. I will open a question if I cannot solve this issue
good method with using the Recordset property, thanks for that hint!
Patrick, the method you shown on your page has a big disadvantage (I tried that too on my own): The value list can only be 32 KB, if you exceed this limit the function will throw an error.
The callback method has the big disadvantage that it is very slow and it is called once for every entry which makes it unuseable for a longer list.
Using the recordset method works very well. I needed this because my SQL string was longer than 32 KB (lot of index values for WHERE ID IN(x,x,x,x,x...)).
Here's a simple function which uses this idea to set a recordset to the combobox:
' Fills a combobox with the result of a recordset.
'
' Works with any length of recordset results (up to 10000 in ADP)
' Useful if strSQL is longer than 32767 characters
'
' Author: Christian Coppes
' Date: 16.09.2009
'
Public Sub fnADOComboboxSetRS(cmb As ComboBox, strSQL As String)
Dim rs As ADODB.Recordset
Dim lngCount As Long
On Error GoTo fnADOComboboxSetRS_Error
Set rs = fnADOSelectCommon(strSQL, adLockReadOnly, adOpenForwardOnly)
If Not rs Is Nothing Then
If Not (rs.EOF And rs.BOF) Then
Set cmb.Recordset = rs
' enforces the combobox to load completely
lngCount = cmb.ListCount
End If
End If
fnADOComboboxSetRS_Exit:
If Not rs Is Nothing Then
If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
End If
Exit Sub
fnADOComboboxSetRS_Error:
Select Case Err
Case Else
fnErr "modODBC->fnADOComboboxSetRS", True
Resume fnADOComboboxSetRS_Exit
End Select
End Sub
(The function fnADOSelectCommon opens an ADO recordset and gives it back. The function fnErr shows a message box with the error, if there was one.)
As this function closes the opened recordset there should be no problem with the memory. I tested it and didn't saw any increasing of memory which wasn't released after closing the form with the comboboxes.
In the Unload Event of the form you can additionaly use a "Set rs=Me.Comboboxname.Recordset" and then close it. This should not be necessary regarding memory, but it may be better to free up open connections (if used with a backend database server).
Cheers,
Christian
A combo box control does not have a recordset property. It does have a RowSource property but Access is expecting a SQL string in there.
You can change the RowSourceType to the name of a user defined "callback" function. Access help will give you more information including sample code by positioning yourself on the RowSourceType and pressing F1. I use this type of function when I want to give the users a list of available reports, drive letters, or other data that is not available via a SQL query.
I don't understand what you mean by your third paragraph with respect to using data directly from the server side. Or rather I don't understand what the problem is with using standard queries.
In MS Access, it's ok, but in VB, you may use something like this using adodc (Jet 4.0):
Private sub Form1_Load()
with Adodc1
.commandtype = adcmdtext
.recordsource = "Select * from courses"
.refresh
while not .recordset.eof
combo1.additem = .recordset.coursecode
.recordset.movenext
wend
end with
End Sub