Is it possible to use a Pivot Table in a userform? - vba

Is it possible to insert a pivot table into a userform in VBA? I saw this other question about it, but I'm able to find the Microsoft Office PivotTable control in the right-click menu. I did find the Tree View, but that isn't quite the same thing I don't think.
UPDATE
I'm creating an Inventory workbook for use in my office. I'll allow others to use it to see what we have and to request items that we have in inventory. I'm going to use the Userform for this. I have a dashboard for myself in the workbook that has several pivot tables already. There are 2 that I'd like to use in the Userform.
The regular users won't have access to edit the workbook, or even to change which sheet is showing, they only need access to view the 2 pivots that I want to add to this Userform.
So, the end result is going to be that the end user will have a pivot table that will allow them to see what we have in inventory and request it or send an email that will create a PO to order it.

I've been using Excel for a very long time and I've never heard of anyone need this combination (UserForm+PT), but anyway, I did a quick Google search and came up with this.
Option Explicit
Dim cnnConnection As Object
Private Sub Form_Load()
Dim strProvider As String
Dim view As PivotView
Dim fsets As PivotFieldSets
Dim c As Object
Dim newtotal As PivotTotal
strProvider = "Microsoft.Jet.OLEDB.4.0"
' Create an ADO object
Set cnnConnection = CreateObject("ADODB.Connection")
' Set the provider and open the connection to the database
cnnConnection.Provider = strProvider
cnnConnection.Open "C:\pivottest.mdb"
' Set the pivot table's connection string to the cnnConnection's connection string
PivotTable1.ConnectionString = cnnConnection.ConnectionString
' SQL statement to get everything from table1
PivotTable1.CommandText = "Select * from table1"
' Get variables from the pivot table
Set view = PivotTable1.ActiveView
Set fsets = PivotTable1.ActiveView.FieldSets
Set c = PivotTable1.Constants
' Add Category to the Row axis and Item to the Column axis
view.RowAxis.InsertFieldSet fsets("Category")
view.ColumnAxis.InsertFieldSet fsets("Item")
' Add a new total - Sum of Price
Set newtotal = view.AddTotal("Sum of Price", view.FieldSets("Price").Fields(0), c.plFunctionSum)
view.DataAxis.InsertTotal newtotal
view.DataAxis.InsertFieldSet view.FieldSets("Price")
' Set some visual properties
PivotTable1.DisplayExpandIndicator = False
PivotTable1.DisplayFieldList = False
PivotTable1.AllowDetails = False
End Sub
Private Sub Form_Terminate()
' Remove reference to the ADO object
Set cnnConnection = Nothing
End Sub
Private Sub PivotTable1_DblClick()
Dim sel As Object
Dim pivotagg As PivotAggregate
Dim sTotal As String
Dim sColName As String
Dim sRowName As String
Dim sMsg As String
' Get the selection object you double-clicked on
Set sel = PivotTable1.Selection
' If it is a aggregate, you can find information about it
If TypeName(sel) = "PivotAggregates" Then
' Select the first item
Set pivotagg = sel.Item(0)
' Display the value
MsgBox "The cell you double-clicked has a value of '" & pivotagg.Value & "'.", vbInformation, "Value of Cell"
' Get variables from the cell
sTotal = pivotagg.Total.Caption
sColName = pivotagg.Cell.ColumnMember.Caption
sRowName = pivotagg.Cell.RowMember.Caption
' Display the row and column name
sMsg = "The value is " & sTotal & " by " & sRowName & " by " & sColName
MsgBox sMsg, vbInformation, "Value Info"
End If
End Sub
See if you can adapt that concept to your specific setup.
https://support.microsoft.com/en-us/help/235542/how-to-use-the-pivottable-office-web-component-with-vb

Related

Skip empty fields when adding a new record using vba

I have a form that users open to add new fees and services for clients. There are 10 fields for fees and I have it setup to auto-populate the fee verbiage if the user un-checks a "standard hourly rate" check box.
If they un-click Std-Hourly, then the field [Fee001] will go from being greyed out and blank, to display "Services # 1 through # 3...." and the user can edit the text if they need it to show "Services # 1 through #7" (depending on how many services the client has)
When the user clicks the Save button, I have the data then saved to TblFee_ByClient table. The table has a record for each service ([ClientID] and [Service] are the only 2 fields in this table) but the form itself has 10 fields ([Fee001], [Fee002], Fee003] and so on) that the user can edit/generate.
All works except my code also adds records for the fields that the user clears/deletes so that I have a record with [ClientID] and no service listed. How do I add code to skip fields that are empty? Here is the vba so far and I am getting an error "Argument not optional" on the "Move.Next":
Private Sub Toggle154_Click()
Dim TblFee_ByClient As DAO.Recordset
Set TblFee_ByClient = CurrentDb.OpenRecordset("SELECT * FROM [TblFee_ByClient]")
TblFee_ByClient.AddNew
TblFee_ByClient![ClientID] = Me.ClientID.Value
If Me.Fee001.Value Is Not Null Then
TblFee_ByClient![Fee] = Me.Fee001.Value
Else Move.Next
End If
TblFee_ByClient![ClientID] = Me.ClientID.Value
TblFee_ByClient![Fee] = Me.Fee002.Value
TblFee_ByClient.Update
TblFee_ByClient.Close
Set TblFee_ByClient = Nothing
End Sub
Correct syntax is TblFee_ByClient.MoveNext but don't see it's needed here.
Use IsNull() in VBA. Is Null is for queries. Or better, handle possibility of Null or empty string.
Don't open recordset with existing records.
Need a conditional statement for each of the 10 controls. This can be accomplished in a loop.
Private Sub Toggle154_Click()
Dim TblFee_ByClient As DAO.Recordset
Dim x As Integer, strFee As String
Set TblFee_ByClient = CurrentDb.OpenRecordset("SELECT * FROM [TblFee_ByClient] WHERE 1=0")
With TblFee_ByClient
For x = 1 To 10
strFee = "Fee" & Format(x, "000")
If Nz(Me(strFee), "") <> "" Then
.AddNew
!ClientID = Me.ClientID
!Fee = Me(strFee)
.Update
End If
Next
.Close
End With
Set TblFee_ByClient = Nothing
End Sub

Query error on a recordset.update vba line

I am trying to print multiple labels for multiple records. The number of labels is consistent for any one run of the label report and all labels for the same record need to be together on the printed sheet. Parameters are entered on TakeNoticeForm and once a button is clicked a recordset, rsQuery, is created with TakeNoticeLabelQuery. Another recordset is created, rsTable, based on a table, TemporaryTNLabels. This table is a copy of my main table, Certificates, without data. I'm using nested For loops to parse through the query results and add "x" copies of said record into the temp table, which will then be used to print the labels. Once the labels are printed the data will be cleared from the temp table for use again later.
Everything I have so far appears to work until I actually start adding data to my temp table. I get Error 3991 - "The query failed to execute because the identifier '[Certificates].[TownshipID]' could not be found" and it points to .Update. [TownshipID] is a lookup field in the Certificates table that was the original for TemporaryTNLabels. I tried to keep the copy intact for possible reuse with other reports but I don't need that field for this report so deleted the lookup field from the temp table to hopefully solve the problem. TakeNoticeLabelQuery is actually a copy of another query, TakeNoticeQuery, that did reference Township information. Again, I was hoping to reuse objects but made a copy and only kept what I needed, which has no reference to TownshipID.
After stripping everything unnecessary away, I can't figure out why it's still trying to find [TownshipID]. I'm still trying to wrap my head around recordsets so wondering if the problem is actually elsewhere, buty I'm confused as to how this error is even remotely related to my code. Any help is appreciated. The SQL for the query and code for generating label data are below.
SELECT Certificates.DatabaseID, Certificates.CertCounty, Certificates.TaxYear, Certificates.ParcelNumber, Certificates.MailToFirstName, Certificates.MailToLastName, Certificates.MailToAlso, Certificates.MailToCity, Certificates.MailToState, Certificates.MailToZip
FROM Counties INNER JOIN Certificates ON Counties.ID = Certificates.CertCounty
WHERE (((Certificates.DatabaseID) Between ([Forms]![TakeNoticeForm]![FirstDBTextbox]) And ([Forms]![TakeNoticeForm]![LastDBTextbox])) AND ((Certificates.CertCounty) Like [Forms]![TakeNoticeForm]![CountyCombobox] & '*') AND ((Certificates.TaxYear) Like [Forms]![TakeNoticeForm]![TaxYearTextbox] & '*')) OR (((Certificates.CertCounty) Like [Forms]![TakeNoticeForm]![CountyCombobox] & '*') AND ((Certificates.TaxYear) Like [Forms]![TakeNoticeForm]![TaxYearTextbox] & '*') AND ((IsNull([Forms]![TakeNoticeForm]![FirstDBTextbox]))<>False) AND ((IsNull([Forms]![TakeNoticeForm]![LastDBTextbox]))<>False));
Option Compare Database
Option Explicit
Private Sub TNLabelPreviewButton_Click()
Dim iTab As Integer
Dim iLabel As Integer
Dim numLabels As Integer
Dim totalRecords As Long
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rsTable As DAO.Recordset
Dim rsQuery As DAO.Recordset
' Set query definition for creating recordset
Set db = CurrentDb()
Set qdf = db.QueryDefs("TakeNoticeLabelQuery")
If CurrentProject.AllForms("TakeNoticeForm").IsLoaded Then
qdf.Parameters("[Forms]![TakeNoticeForm]![FirstDBTextbox]") = [Forms]![TakeNoticeForm]![FirstDBTextbox]
qdf.Parameters("[Forms]![TakeNoticeForm]![LastDBTextbox]") = [Forms]![TakeNoticeForm]![LastDBTextbox]
qdf.Parameters("[Forms]![TakeNoticeForm]![CountyCombobox]") = [Forms]![TakeNoticeForm]![CountyCombobox]
qdf.Parameters("[Forms]![TakeNoticeForm]![TaxYearTextbox]") = [Forms]![TakeNoticeForm]![TaxYearTextbox]
' qdf.Parameters("[Forms]![TakeNoticeForm]![TakeNoticeDateTextbox]") = [Forms]![TakeNoticeForm]![TakeNoticeDateTextbox]
Else
qdf.Parameters("[Forms]![TakeNoticeForm]![FirstDBTextbox]") = ""
qdf.Parameters("[Forms]![TakeNoticeForm]![LastDBTextbox]") = ""
qdf.Parameters("[Forms]![TakeNoticeForm]![CountyCombobox]") = ""
qdf.Parameters("[Forms]![TakeNoticeForm]![TaxYearTextbox]") = ""
' qdf.Parameters("[Forms]![TakeNoticeForm]![TakeNoticeDateTextbox]") = CStr(Date)
End If
Set rsQuery = qdf.OpenRecordset
rsQuery.MoveLast
totalRecords = rsQuery.RecordCount
'Close and delete records from TemporaryTNLabels table.
DoCmd.SetWarnings False
DoCmd.Close acTable, "TemporaryTNLabels"
DoCmd.RunSQL "DELETE FROM [TemporaryTNLabels]"
DoCmd.SetWarnings True
numLabels = Me.NumLabelsTextbox
MsgBox numLabels & " labels"
'Open a table-type Recordset
Set rsTable = db.OpenRecordset("TemporaryTNLabels", dbOpenTable)
rsQuery.MoveFirst
With rsTable
For iTab = 1 To totalRecords
For iLabel = 1 To numLabels
Debug.Print rsQuery!DatabaseID
.AddNew
!ParcelNumber = rsQuery!ParcelNumber
.Update ' <-------------------------This is where the error points.
.Bookmark = .LastModified
Next iLabel
rsQuery.MoveNext
Next iTab
End With
' DoCmd.OpenReport ReportName:="TakeNoticeLabelReport", View:=acViewPreview
rsTable.Close
Set rsQuery = Nothing
Set qdf = Nothing
Set db = Nothing
End Sub
This is why I stopped using lookup fields :-( I would delete the table TemporaryTNLabels or rename it to TemporaryTNLabels_OLD. Then recreate the table TemporaryTNLabels from scratch with only one field ParcelNumber, and start from there.

Access 2010 - VBA - Editing underlying table data using text boxes on a form

I'm using too many boolean indicators and I'm sure its very inefficient/stupid...
Currently in the Access database I have numerous forms which are used to edit underlying records. Text boxes on these forms are not bound to the underlying table. I do not wish to bind the form or any of its controls directly to the underlying tables, if the data is editable by the user (less human error from users). Instead I've a Boolean for every control which contains editable information.
Users enter 'edit mode', change information (Boolean now equals true), click 'save changes', review the changes and accept and then the relevant queries are run to reflect these changes. I like this order of events however, I'm creating increasingly complex forms with 40 or so editable controls and hence 40 Boolean variables.
Anyone think of an nicer alternative? Is there a property of the controls (mainly text boxes) I can use?
CODE:
Private Sub CommentsText_AfterUpdate()
If Nz(Me.CommentsText) = "" Then
CommentsEdit = False
Else
CommentsEdit = True
End If
End Sub
'Within the 'save changes' method
If CommentsEdit Then
CommentsEdit = False
sql = "Update [General-CN] Set [Comments] = '" & Left(Me.CommentsText, 250) & "' Where [ID ( G )] = " & Me.[GeneralPK] & ";"
DoCmd.RunSQL (sql)
End If
normally data validation during data entry is one the things that normally, in my experience, before or later put the developer on his knees.
From your question, I can feel that the core is to preserve database integrity. Unfortunately there is no way to ABSOLUTELY preserve your database. If you give access to it to users you can only use some tips:
1 - If possible use combo-box with defined entries instead of using textboxes in which user is free to digit anything (e.g. think an expert system that collect data about the same problem written in many different ways!!!)
2 - Check data integrity and coherence (e.g. type) before writing it to database
3 - When the data is just a boolean (flag) you can use switches, radio button or checkboxes.
These tips help in developing a user interface more friendly and faster from the point of view of data entry.
After this I can give you another way to validate your data.
If you want to show data to user before saving you can create the mask in which you enter your data with unbounded textboxes.
Then, when he clicks the Save Button, you can show a 2nd form, opened in append mode, in which you show data with textboxes bounded to your db.
If he accepts the data, you can save otherwise you can cancel the data entry preserving your database. I post you here some lines of code with an example taken from an application of mine. It's the part in which I manage contacts
The form allows to input contact data
'------------------------------------------------------
' Temp variables to store entries before saving /cancelling
'------------------------------------------------------
Dim bolValidationErr As Boolean
Dim m_vntLastName As Variant
Dim m_vntFirstName As Variant
Dim m_vntFC As Variant
Dim m_vntVATNum As Variant
Dim m_vntAddress As Variant
Dim m_vntCity As Variant
Dim m_vntZIP As Variant
Dim m_vntCountry As Variant
Dim m_vntPhone As Variant
Dim m_vntFAX As Variant
Dim m_vntEMail As Variant
Dim m_vntNote As Variant
Dim m_vntContactType As Variant
'------------------------------------------------------
' Suppress error "Impossible to save the record...
'------------------------------------------------------
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = True
End If
End Sub
'------------------------------------------------------
' W/o customer last name, cancel saving
'------------------------------------------------------
Private Sub Form_Beforeupdate(Cancel As Integer)
On Error GoTo Err_Form_BeforeUpdate
Dim strType As String
Dim bolNewContact As Boolean
Dim intErrFC As Integer
Dim intErrVATNum As Integer
Dim intErrFullName As Integer
Dim strErrMsg As String
Dim intAnswer As Integer
bolValidationErr = False
'------------------------------------------------------
' If LastName is missing, cancel data saving
' Cancel = True cause the raise of the error
' "You can't save record at this time..."
' SetWarnings doesn't work. It's needed to intercept with Form_Error event
'------------------------------------------------------
If HasNoValue(Me.txtLastName) Then
strErrMsg = "Put here your error msg"
intAnswer = MsgBox(strErrMsg, vbOKOnly + vbExclamation, "Entry Error")
Cancel = True ' Cancel db update
Call BackupCurrentData ' Store data input until now
bolValidationErr = True ' Unvalid data
Exit Sub
End If
Exit_Form_BeforeUpdate:
Exit Sub
Err_Form_BeforeUpdate:
GoTo Exit_Form_BeforeUpdate
End Sub
'------------------------------------------------------
' Store the content of textboxes for restoring them in case
' of cancelling
'------------------------------------------------------
' If the record is new, if the BeforeUpdate event is cancelled,
' NULL values are restored (in fact there were no data!!!)
'------------------------------------------------------
Private Sub BackupCurrentData()
m_vntLastName = Me.txtLastName
m_vntFirstName = Me.txtFirstName
m_vntFC = Me.txtFC
m_vntVATNum = Me.txtVATNum
m_vntAddress = Me.txtAddress
m_vntCity = Me.txtCity
m_vntZIP = Me.txtZIP
m_vntCountry = Me.txtCountry
m_vntPhone = Me.txtTelNum
m_vntFAX = Me.txtFax
m_vntEMail = Me.txtEmail
m_vntNote = Me.txtNotes
m_vntContactType = Me.cmbContactType
End Sub
'------------------------------------------------------
' Restore contents of textboxes before cancelling operation
'------------------------------------------------------
Private Sub RestoreCurrentData()
Me.txtLastName = m_vntLastName
Me.txtFirstName = m_vntFirstName
Me.txtFC = m_vntFC
Me.txtVATNum = m_vntVATNum
Me.txtAddress = m_vntAddress
Me.txtCity = m_vntCity
Me.txtZIP = m_vntZIP
Me.txtCountry = m_vntCountry
Me.txtTelNum = m_vntPhone
Me.txtFax = m_vntFAX
Me.txtEmail = m_vntEMail
Me.txtNotes = m_vntNote
Me.cmbContactType = m_vntContactType
End Sub
I think that this code can be adapted to your needs.
The variable names are enough self-describing
Anyway feel free to contact me if you need further help.
Bye,
Wiz:-)

Extract and display data from access table using VBA

I have a function to extract and then display a recordset in a listbox.
I only get one field in my listbox.
Is there a way I can display the whole column "Caption" (several fields) in the listbox?
Function GetCaption() As String
Dim db As Database
Dim rst As DAO.Recordset
Dim SQL As String
Dim LCaption As String
Set db = CurrentDb()
SQL = "SELECT Caption FROM tblMainMenu"
Set rst = db.OpenRecordset(SQL)
If rst.EOF = False Then
LCaption = rst("Caption")
Else
LCaption = "Not found"
End If
rst.Close
Set rst = Nothing
GetCaption = LCaption
End Function
Private Sub btnGetCaption1_Click()
LstBx.RowSourceType = "Value List"
LstBx.RowSource = GetCaption
End Sub
Private Sub Form_Load()
LstBx.RowSource = ""
btnGetCaption1.Caption = DLookup("ReportID", "tblMainMenu", "ReportID = 1")
End Sub
I'm not sure how well I understand your goal. But if you want the list box to contain tblMainMenu.Caption values, one per list box row, you can use the query as its Record Source.
With the form open in Design View, open the list box's property sheet, and select the Data tab. Then choose "Table/Query" for Row Source Type. Add this SQL for the Row Source property.
SELECT [Caption] FROM tblMainMenu
Then select the Format tab, and enter 1 for the Column Count property.
Finally switch to Form View and tell us whether that gives you what you want, or how it differs from what you want.

Creating Tables in Word Programmatically

I am generating tables and writing them to word on the fly. I do not know how many tables there will be each time i write the data to word and the problem I am having is the second table is written inside the first cell of my first table. If there was a third table it is put inside the first cell of my second table.
Is there a way to move the cursor out of the table? I have tried creating a new range with each table also but the same thing happens.
I have also tried things like tbl.Range.InsertParagraphAfter()
The closest I came was using the Relocate method, but this only worked for two tables.
I've had this exact same issue and learned that you have to collapse the Range to the end of the table range, then insert a line break, collapse again and then insert your new table.
Here's some code that uses tables and bookmarks - it is meant to show how to use native vs. VSTO host bookmarks (and adding a click handler to the VSTO one) - but you may just need part of the code instead. Look for
With tbRange
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd)
.InsertParagraphAfter()
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd).Select()
End With
below - that's what you'll need to disallow table-within-table nesting.
Sub Assign3TablesToNativeBookmarks()
'this is the native Word bookmark
Dim bm As Word.Bookmark
Dim tb As Word.Table
Dim tbRange As Word.Range
Dim i As Integer
For i = 1 To 3
bm = Me.Bookmarks.Add(Name:="nestedBookmark" & CStr(i), _
Range:=ThisApplication.Selection.Range)
tb = bm.Range.Tables.Add(Range:=bm.Range, NumRows:=2, NumColumns:=2)
With tb
.Style = "Table Grid"
tbRange = .Range
With tbRange
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd)
.InsertParagraphAfter()
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd).Select()
End With
bm = Me.Bookmarks.Add(Name:="nestedbookmark" & CStr(i), Range:=.Range)
End With
Next
Dim bmMain As Word.Bookmark
Dim mainBookmarkRange As Word.Range
Dim mainBookmarkRangeStart As Integer
Dim mainBookmarkRangeEnd As Integer
mainBookmarkRangeStart = Me.Bookmarks(1).Start
mainBookmarkRangeEnd = Me.Bookmarks(Me.Bookmarks.Count).End
mainBookmarkRange = Me.Range(Start:=mainBookmarkRangeStart, End:=mainBookmarkRangeEnd)
bmMain = Me.Bookmarks.Add(Name:="mainBookmark", Range:=mainBookmarkRange)
End Sub
Sub Assign3TablesToHostControlBookmarks()
'Word host control of Bookmark
'bookmarks must be destroyed before resetting the object
'added handler
Dim bm As Microsoft.Office.Tools.Word.Bookmark
'different from the interop one
Dim tb As Word.Table
Dim tbRange As Word.Range
Dim i As Integer
For i = 1 To 3
bm = Me.Controls.AddBookmark(range:=ThisApplication.Selection.Range, _
Name:="nestedBookmark" & CStr(i))
tb = bm.Range.Tables.Add(Range:=bm.Range, NumRows:=2, NumColumns:=2)
With tb
.Style = "Table Grid"
tbRange = .Range
With tbRange
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd)
.InsertParagraphAfter()
.Collapse(Direction:=Word.WdCollapseDirection.wdCollapseEnd).Select()
End With
bm.Delete()
'this deletes the bookmark before it can be recreated
bm = Me.Controls.AddBookmark(range:=.Range, Name:="nestedBookmark" & CStr(i))
AddHandler bm.Selected, AddressOf bm_Selected
'handler added
End With
Next
Dim bmMain As Microsoft.Office.Tools.Word.Bookmark
Dim mainBookmarkRange As Word.Range
Dim mainBookmarkRangeStart As Integer
Dim mainBookmarkRangeEnd As Integer
mainBookmarkRangeStart = Me.Bookmarks(1).Start
mainBookmarkRangeEnd = Me.Bookmarks(Me.Bookmarks.Count).End
mainBookmarkRange = Me.Range(Start:=mainBookmarkRangeStart, End:=mainBookmarkRangeEnd)
bmMain = Me.Controls.AddBookmark(range:=mainBookmarkRange, Name:="mainBookmark")
End Sub
Private Sub bm_Selected(ByVal sender As Object, ByVal e As Microsoft.Office.Tools.Word.SelectionEventArgs)
MessageBox.Show("Hey, you have selected bookmark: " & sender.Name & ". " & _
"You did this at " & FormatDateTime(Date.Now(), DateFormat.LongTime))
End Sub
The easiest way to insert tables into word is to generate html tables, and then insert this into the file at the point where your cursor is.
It allows for easy creation of arbitrarily complex nested tables without using most of the ridiculously difficult word interop functions.
Where is it that you want to put each new table? At the end of the document? Start your new table at the end of Document.Content.