Forcing a Pivot table to refresh in a very complicated scenario - sql-server

Detailed description inside
Background
I have a spreadsheet with a Pivot table ("PT") that many users will access through my company's web portal. The users are assumed to have little to no experience with Excel and little to no tolerance for "additional steps". So user interaction apart from normal pivot operation won't work.
I am running SQL Server 2008 and using a stored procedure to generate the data. I have to use a stored procedure because I need to be able to pass along a parameter from Excel (this is not ideal but I have numerous safeguards).
Chronological Order of Factors/Solutions
Complication 1
A PT cannot directly connect to a stored procedure like it can tables etc.
Solution This means that the data must first be imported into a table (which can connect to a stored procedure) and then this table will be the source for the PT.
Complication 1A
When the table refreshes (updates data connection) the PT will not refresh itself because it has no trigger event.
Solution: Use VBA and specifically the worksheet change event on the table's sheet module to force refreshes on PT. This works great and all was working 100%.
Complication 2
When a PT whose is within the same workbook and this workbook is opened from a remote location (web portal), you will get an error that looks like "Error 1004: Source file not found". The PT tries to use the path of the workbook to connect to its source but it sees the temporary URL as its path.
Solution: Instead of directly refreshing the PT using PivotCache.Refresh, RefreshTable use Activeworkbook.RefreshAll which for whatever reason does actually work when the former gave me the Error 1004.
Complication 3
So now I need to now detect a change in the table and then use Activeworkbook.RefreshAll. But despite using Application.EnableEvents = False and even setting a boolean to prevent the change event from kicking in when the Activeworkbook.RefreshAll is executed, I am getting an infinite loop.
Worksheet Module:
Private blnAbort As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
If blnAbort = True Then Exit Sub
blnAbort = True
Call Refresh
blnAbort = False
End If
End Sub
Standard Module:
Public Sub Refresh()
Application.EnableEvents = False
ActiveWorkbook.RefreshAll
Application.EnableEvents = True
End Sub
Possible Solution that caused another problem:
It seems like the Activeworkbook.RefreshAll was being executed and then the macro just continued on while it was still refreshing. This seemed to be causing the Enable_Events event and blnAbort to be reset just in time for something to refresh and trigger a change and the whole thing started again. I changed the data connection property BackgroundRefresh to false.
However, now I am getting the error Method 'RefreshAll' of object '_Workbook' failed and the debug is just jumping to Activeworkbook.RefreshAll which is not helpful.
Any ideas?

How are you calling the refresh of the source table? If the user refreshes it via a button then you can just force a refresh of the pivot table's pivotcache...
in the worksheet code-module:
Private Sub BtnRefreshData_Click()
'// Insert the "refreh" routine or call a procedure to refresh the
'// data from the stored procedure here..
'// Once the data is done being refreshed, manually refresh the pivot cache
'// for the pivot table. The code looks like this:
Sheet1.PivotTables("MyPivotTable").PivotCache.Refresh
'// If you have a lot of pivot tables to refresh from your "source" data-table
'// then you can always just do a foreach loop...
Dim sht1 As Worksheet: Set sht1 = Sheet1
Dim pTable As PivotTable
For Each pTable in sht1.PivotTables
pTable.PivotCache.Refresh
Next pTable
End Sub
Let me know if this helps, or be more specific as to why something straight forward like this won't work and ill do my best to try again. If it does work, let me know! thanks, Brian
UPDATE:
So in response to your comments: From what I can tell it sounds like really the only issue you have is the infinite loop created by interwoven events (event that refreshes the pivot table calls the change event, which refreshes the pivot table, etc. etc, etc...). I think the easiest solution for you at this point is to disable all events when the refresh event takes place, which shouldn't fire off the unwanted event (causing the loop). Before the code that refreshes the pivot table put the following code:
'// Start Code Snippet...
Application.EnableEvents = False
'// Do your refresh routine here....
Application.EnableEvents = True
'// End Code Snippet
Does this help? If not I'll try again :) ... thanks, Brian

Related

Connect multiple tables in Access

I want to connect multiple tables in Access. I have a main table and then multiple smaller tables that I would like to connect. Each row in the main table belongs to one entire sub-table. I would like to have small + appear next to each row in the main table and showing the sub-tables upon clicking on the +.
I tried this via relationships but it only works for the first row (perfectly) but not for any other rows. When clicking on the other rows, they are empty and no sub-table is opening up.
Any ideas?
Per the comments if you must do this here is an example:
example main table and sub tables
example form created from the main table using the create form wizard and selecting tabular to get a better looking starting form:
note I added an unbound button to the details section and that button gets replicated for each row of data. I will show two ways of setting up the button to open the correct form.
put the form in design mode open the button's properties and open the onclick event. you can do this with the macro language but I show the code:
Private Sub openButton_Click()
'each button the detail section creates has access to the data or controls in the buttons row
DoCmd.OpenTable Me.subTableName 'if form use .OpenForm
'DoCmd.Close acForm, "mainForm" 'if want to close main form
'OpenTable (MainID)
End Sub
if the logic behind opening the form gets complicated I suggest abstracting the logic to a function. here use OpenTable instead and add the Public OpenTable function inside a code module.
Public Function OpenTable(tableID As Long) As Boolean 'function allows the option checking the return value to test if OpenTable worked
Select Case tableID
Case 1
DoCmd.OpenTable "subTable1"
Case 2
DoCmd.OpenTable "subTable2"
Case 3
DoCmd.OpenTable "subTable3"
End Select
OpenTable = True
End Function
Note: you could expand the form's header and show the mainTable as a subform in that header. Then you could add a button next to each row in that subform but outside the subform. The approach I show is easier to maintain.

Order sub to click userform commandbutton from sub

I am writing here because I cannot seem to find the answer on the internet.
Context of my sub and userform : I have several pivottables in the same sheet in a workbook.
These pivottables may have random names given by other users changing them unintentionally or not knowing the names are essential to the rest of the code. This is why i created a userform which has on one side the ideal name of each pivottable, and on the other side, comboboxes with the current names, like so:
pivottable dealing with x // combobox with all current names (user has to choose which one of the current names corresponds to the this pivottable)
pivottable dealing with y // combobox with all current names (user has to choose which one of the current names corresponds to the this pivottable)
pivottable dealing with z // combobox with all current names (user has to choose which one of the current names corresponds to the this pivottable)
and finally a button "VALIDATE"
here is a screenshot of my userform : enter image description here
WHAT I AM TRYING TO DO :
since the user has to inform the userform which pvttable corresponds to which title, i have written :
userform1.show vbmodeless
so that the user can go check the current titles of the pivottables and associate them with the correct one. Because I have put the vbModeless, the sub does not wait for the user to validate and thus fills the title with nothing or blank as nothing from the combbox has been chosen since the user hasn't had the time yet. Therefore, I want to signal that once and only once i clicked on "valider", then the sub changes the current titles to the ideal titles (can only do in the sub and not in the userform because i actually use an array that is subject to changes in the ideal names, and I can't put it in the userform because it is defined above in the sub, ican't pass it over to the userform). I am triyng to use something like :
if userform1.commandbutton1.click = true then
'and put pvtnames = userform1.combobox1.value
(but i am alright with the pvtname part). IS THERE A WAY TO CODE THE BUTTON.CLICK PART ? thank you ever so much for your help, i am sorry for the long message

How can I populate a combobox from a SQL Server database server in Delphi?

First of all, I'm new to Delphi, and I checked all the relevant threads on here, but nothing seems to be working.
I'm just trying to fill a TComboBox with values of a column from a SQL Server database.
So basically, I have a VCL form and a DataModule, which holds an ADOConnection and an ADOQuery. The ADOConnection connects to my database (test connection says it's successful) and the ADOQuery connects to my ADOConnection. The VCL form and the DataModules are "connected".
(And actually here comes another problem: whenever I'm trying to enter SELECT name FROM Partners; in the properties of my ADOQuery and try to set it active, it keeps saying "Invalid object name 'Partners'". The SQL statement is correct, I tested it in SSMS and it retrieves the data without any error. But I'm not sure if it's the main issue here).
So basically, here's my code:
procedure TFormInsertNewItem.ComboBoxPartnersDropDown(Sender: TObject);
begin
with DataModule1 do
begin
ComboBoxPartners.Items.Clear;
ADOQueryFillPartners.SQL.Clear;
ADOQueryFillPartners.SQL.Add('SELECT name FROM Partners;');
ADOQueryFillPartners.Active:=true;
ADOQueryFillPartners.Open;
while not ADOQueryFillPartners.Eof do begin
ComboBoxPartners.Items.Add(ADOQueryFillPartners.FieldByName('name').AsString);
ADOQueryFillPartners.Next;
end;
ADOQueryFillPartners.Active:=false;
ADOQueryFillPartners.Free;
end;
end;
Whenever I click to drop it down (or maybe it would be better if this runs when the form is loaded, but I have no idea how can I exactly refer to that event), it should fill it up. Well, it won't. I tried it with TDBComboBox and TDBLookUpComboBox, with setting their datasource, but it only added 1 item to the combobox.
Can anyone help me with what I messed up and how can I get it to work, please?
You have multiple problems in the code you've posted.
Clearly, the first thing you have to do is to get your query to open properly. If you can't run the query to get the data, you can't use that data to populate the combobox. Test your ADOConnection on your datamodule to see if you can connect to the database by setting its Connected property to True.
If that doesn't work, then you need to set up your ConnectionString properly. (Make sure you set the Connected property back to False. You should be connecting in your datamodule's OnCreate event, and disconnecting in the datamodule's OnDestroy event.)
If it does work, check to make sure you set the ADOQuery's Connection property to point to your ADOConnection, and then use the ADOQuery.SQL property to enter your SQL statement. Once you've done that, set the ADOQuery.Active property to True. (Again, don't forget to set it back to False and open it in your code at runtime.)
Next, you've used the wrong event. You should be doing this in the form's OnCreate event instead.
Also, you don't need both Active and Open for the query. They do the same thing; the only difference between them is that Active is a Boolean property that can be tested (if Query1.Active then) while Open is simply a method. There's no reason to open a query that's already active or make a query active that's already open.
In addition, you free the query at the end of your method, which means that the next time the combobox OnDropDown event is called, you're accessing an invalid class instance, which will cause an access violation.
And finally, you should be calling BeginUpdate before clearing and populating the combobox, and then EndUpdate when you're finished.
A more correct version of the code would be something like
procedure TFormInsertNewItem.FormCreate(Sender: TObject);
begin
with DataModule1 do
begin
{
I'm not sure why you're doing this at all. If you set the ADOQuery.SQL property
at designtime, and it never changes, you can remove the next two lines.
}
ADOQueryFillPartners.SQL.Clear;
ADOQueryFillPartners.SQL.Add('SELECT name FROM Partners;');
ADOQueryFillPartners.Active:=true;
try
ComboBoxPartners.Items.BeginUpdate;
while not ADOQueryFillPartners.Eof do begin
ComboBoxPartners.Items.Add(ADOQueryFillPartners.FieldByName('name').AsString);
ADOQueryFillPartners.Next;
end;
finally
ComboBoxPartners.Items.EndUpdate;
end;
ADOQueryFillPartners.Active:=false;
end;
end;

Unable to pass value from one form to another - Ms access

I have a multiple VBA forms, the first form takes user's name from a combo box and saves it in a global variable, code is below :
Option Compare Database
Public emplID_global
Private Sub OKLogin_Click()
If Not IsNull(Me.Combo_emplID) Then
emplID_global = Me.Combo_emplID.Column(1)
DoCmd.Close
DoCmd.OpenForm "NavFrm", acNormal
End If
End Sub
Now in my other form I have a combo box in which I want to auto-populate the valueof that variable, on load of form, which i saved using previous form, below is the code :
Private Sub Form_Load()
Me.AdvName = emplID_global
Me.DataForm.Form.Recordset.MoveLast
End Sub
but it is not working, It is not doing anything. I want to use this kind of code in all the forms.
Can someone please suggest what is the mistake in my code, i am fairly new to VBA. Thanks in advance :)
Add a field to your underlying table for the owner of that particular record. (I just called it Owner.)
Add the following code to your form:
Private Sub Form_BeforeInsert(Cancel As Integer)
Owner = Environ("username")
End Sub
Private Sub Form_Current()
Dim editable As Boolean
if nz(Owner,"")="" then
editable=true
else
editable = (Environ("username") = Owner)
End If
Form.AllowDeletions = editable
Form.AllowEdits = editable
End Sub
This is a fairly generic solution that does the job. Environ("username") returns the user's windows username. This is not necessarily all that secure. A person could go to the command prompt and edit the username environment variable so that they appear to be another person. This will definitely not keep out the NSA or anyone else with a little computer knowledge and bad intentions. But it will keep the honest people honest.
Explanation:
The BeforeInsert runs as a new record is being saved. In this case, it sets the record's Owner field to the user's Windows username.
The OnCurrent portion runs every time a new record is displayed. The code will check to see if the current Windows username matches the username stored in the record's Owner field and set the forms AllowEdits and AllowDeletions flags accordingly. Also, if the Owner field is null (as in the case of a new record), editable will be set to true. If you wanted to really emphasize the fact that the user shouldn't be changing things, you could also enable/disable the individual text boxes...
TextBox1.Enabled=editable
ComboBox2.Enabled=editable
... etc.
Simple solution. Keeps honest people from overwriting each others' work. I use this approach a lot.

VB2010 reading menu items in tool strip sub menu

SQL Server/VB2010 newbie. Fighting the learning curve on two fronts.
I am creating a UI in VB2010 to a SQL Server database that will allow users to edit tables in the database. Since the tables in the database may change (new ones added, or obsolete ones deleted) as the project develops, the table names are dynamically added to the sub menu, as shown in the screen cap below:
Then I use the AddHandler function to run the click event sub. That works fine, but what I need to do is grab the actual text of the submenu item ("VehicleList", "Maintenance", etc.) to use in the SQL SELECT string to open the selected table in the database, like so:
"SELECT * from [selected_menu_item] WHERE yada, yada"
I've scoured the internet looking for this, but if it's there, I can't find it. Tried many ways, but I either get the top menu item ("Table") or the last item on the list, or sometimes just a blank line.
Preferable would be to get the actual names, but even if there was an index value, I could use that to reference an array of the table names.
Can anyone help?
Thanks!
The answer is to use the "ByVal e As ToolStripItemClickedArgs" argument in the handler, like this:
Private Sub MenuItem_click(ByVal sender As System.Object, ByVal e As ToolStripItemClickedEventArgs)
Debug.WriteLine(e.ClickedItem.Text)
End Sub
Found this at http://msdn.microsoft.com/en-us/library/system.windows.forms.toolstripitemclickedeventargs.clickeditem.aspx
It works exactly as I need it to!

Resources