excel listObject issue - vba

I have inherited a spreadsheet full of VBA I know next to nothing about.
When the data is refreshed, the spreadsheet re-formats itself via the VBA. I had to change some tab names and table names and now when the auto reformat kicks in it gives an error as it is looking for the old table name.
Sorry if this s a bad explanation/terminology, I really never get involved with VBA usually!
So the error I get is
Run-time error '450': Wrong number of arguments or invalid property assignment
debug takes me to this line
For Each rptTable In sID.ListObjects
rptTable.TableStyle = teamName <--- this is the highlighted line in debug
oldSize = rptTable.ListRows.Count
If oldSize < teamSize Then
teamName has the new value, rptTable.TableStyle has the old value.
the variables are defined as follow i believe:
Sub FormatSheet(sID As Worksheet)
'Clear debugWindow
Call clearDebugConsole
Application.StatusBar = "Formatting Teamsheet ... "
'Change Active Sheet to the Sheet being Formatted
Worksheets(sID.Name).Activate
Debug.Print "* Entered FormatSheet Sub"
' Set Variables
....
Dim rptTable As ListObject
I basically trying to work out how the ListObject is being populated and where the old name is being picked up from? a global search in the excel spreadsheet doesn't pick it up

Well, it seems that teamname has a value that does not correspond to anything in TableStyles.
stop the code on that line and see what value variable teamname holds. There needs to be value that exists in your table styles. My guess is there is empty string. You need to assign it something like "TableStyleMedium2" etc.. You can find these names in the design ribbon -> table styles. Use the value without spaces.

Related

Unable to assign value from record set to vba variable (error -2147217887 (80040e21))

I have an excel workbook, for many reasons I am using sql into excel to loop through fields, the workbook, contains values about employees.
I created a record set from the sheet that I am working on with this request :
Set rst = OpenRecordset("SELECT * FROM [Feuil1$]")
this will load my sheet into a record set.
everything were making well till I passed my excel to a friend and he copy pasted data from another sheet into my excel and launched my macro and here I discovered a bug.
When I reach the column "G" Client the line number 13, and try to assign it to a variable, i get an error
I have a variable called Client in which I fill the value of the field client
Dim rst As Object
Dim Client As Variant
Set rst = OpenRecordset("SELECT * FROM [Feuil1$]")
While Not rst.EOF
...
Client = rst.Fields("Client").Value
..
rst.movenext
Wend
Set rst = Nothing
the line of code causing the error is :
Client = rst.Fields("Client").Value
I get an error telling -2147217887 (80040e21)
"you cannot record changes because a value you entered violates the settings defined for this table or list (for example, a value is less than the minimum or greater than the maximum). correct the error and try again"
Sorry the message in the screen capture is in french cuz the workbook that i am working on it is french but here is the translation to english
This made me trying to understand what has specific the line 13 in the column "G"
First of all I can see that this problem appears at the first line filled with data (ATS) in that colum which is the line 13 .. the previous cells in previous lines were empty and the code was working fine till line 13.
Client = rst.Fields("Client").Value
At my own original sheet, there is no kind of error, the error came just after my friend copy/pasted his own values into the sheet, and after looking deeply I noticed that after he copy/pasted the data, the format of the cells in the colum G changed to numbers (double) as seen in this picture
But my variable is variant, so it should accept any kind of data (empty or not, double or not) that's why I can't understand why it's giving this problem ?
I said maybe Excel is not loading into the record set the value ATS, maybe it's loading inside the Rs a kind of special error or something like that, so that the variant variable can't contains it and give this error, I really don't know that why I am asking if you have any clue about this error and how to solve it by code because I simply can solve it manually by changing back to what was the format of my cells before that my friend pasted his data, so if i change the format to Normal instead of decimal in excel cells the problem is solved, but what I want to know is to understand why this error, and how to solve it by CODE inside vba not excel sheet and why a variant variable won't accept a string value (ATS) even if the format in excel is decimal ?
One last thing, i tried to do something like the following but still getting the same error
If Not IsError(rst.Fields("Client").Value) Then Client = rst.Fields("Client").Value Else Client= vbNullString
Also, If I add a spy to the line causing the problem, I find inside the value, the same error message shown in my vba msgbox, here is a printscreen
: "
"you cannot record changes because a value you entered violates the settings defined for this table or list (for example, a value is
thanks in advance to anyone who will have a constructive and usefull answer.

Programmatically Update Linked Named Range of excel object in MS Word (2007)

First question, excuse me if this has already been solved, but I've searched thoroughly and cannot find an answer:
I have linked several named ranges into a word document. This word doc (and the related excel workbook with named ranges) is a template: it's for a coworker who will make many copies of these templates (of both the word doc and the excel workbook).
I would like to include a command button in the word doc that, when clicked, will update the sources for the linked named ranges. Specifically, I want it to set the workbook with the same name as the worddoc, as the source.
The issue is that it does not like the named range I have entered. I get the:
Run-time error '6083': Objects in this document contain links to files that cannot be found. The linked information will not be updated.`
However, I have quadrupled-checked my excel doc, the named range exists. AND, when I hit Alt+F9 in word, I clearly see the link contains the named range!
{LINK Excel.Sheet.8 C:\Users\Marc\Documents\WIP_SSS.xlsm CED \a \p}
Here is my code:
Public Sub ChangeSource()
Dim filename As Variant
Dim fieldcount As Integer
Dim x As Integer
filename = Left(Application.ActiveDocument.Name, Len(Application.ActiveDocument.Name) - 4) & "xlsm"
fieldcount = ActiveDocument.Fields.Count
For x = 1 To fieldcount
'Debug.Print ActiveDocument.Fields(x).Type
If ActiveDocument.Fields(x).Type = 56 Then
ActiveDocument.Fields(x).LinkFormat.SourceFullName = ActiveDocument.Path & "\" & _
filename & "!CED"
End If
Next x
End Sub
If I don't enter the named range at all, the macro works, but it embeds the entire excel worksheet (which I do not want it to do). Any ideas on how/ why it is not liking the named range?
Thanks,
Marc
UPDATE:
With help from Bibadia, I found a solution; in addition, I want to document some strange behavior exhibited by Word VBA:
First off, the solution code:
Public Sub ChangeSource()
Dim filename As Variant
Dim fieldcount As Integer
Dim x As Integer
filename = ThisDocument.Path & "\" & Left(Application.ActiveDocument.Name, Len(Application.ActiveDocument.Name) - 4) & "xlsm"
fieldcount = ActiveDocument.Fields.Count
For x = 1 To fieldcount
On Error Resume Next
If ActiveDocument.Fields(x).Type = 56 Then
ActiveDocument.Fields(x).Delete
End If
Next x
ActiveDocument.Bookmarks("R1").Range.InlineShapes.AddOLEObject filename:=filename & "!Range1", LinkToFile:=True
End Sub
I first deleted all type 56 fields (linked object, or more technically, "wdfieldlinked"). Then, I added OLEObjects at pre-set bookmark locations.
Interestingly, just as Bibadia noted, the key was to input the LinkToFile:=True code. It seems Word will not accept the object if it is embedded: if I remove that line, I get the error Word Cannot obtain the data for the C:\...\document!NamedRange link.
Finally, I found one other odd behavior: When trying to simply replace the link, using this code,
ActiveDocument.Fields(1).LinkFormat.SourceFullName = filepath+name & _
"!CED" 'that is the named range
it would work once, when I changed both the word document's and the excel workbook's filenames (see original message for context). So, when the new filepath+name DID NOT match the existing filepath+name, Word VBA accepted the change. However, once initially updated, if I tried to run the macro again, I would get:
run-time error '6083': Objects in this document contain links to files that cannot be found. The linked information will not be updated.
I would get this error even if I changed the named range to another named range in the same worksheet (and obviously same workbook). So it appears that Word VBA does not like "updating" filepath+name when the filepath+name does not change.
Just so anyone who didn't know (like me) now knows. Sorry for the long update, I just wanted to be thorough.
I am not completely sure of this, but it is a little too long for a comment.
As far as I know, you can only set LinkFormat.FullSourceName to the name of a file, not a fullname + subset name, which is what you are trying to do when appending the "!CED". Although you can read the subset name (CED) from OleFormat.Label, you can't modify it as it's a read-only property.
So if you actually need to modify the subset name (CED), AFAICS the only way to do it is to delete and reinsert the LINK field. If you reinsert using Fields.Add, you just specify the text of the field, so you can get the file name and Subset name right. What is slightly confusing is that if you insert a LINK using InlineShapes.AddOleObject, you can specify fullname+subset name in exactly the way that you are trying to do in your code.
However, I do not think you are trying to modify the Subset name. So let's assume that you already have a LINK field along the lines of
{ LINK Excel.SheetMacroEnabled.12 "the full pathname of a .xlsm file" CED \a f 0 \p }
Word will only be able to update that link if the path+filename is valid (i.e. there's a .xlsm at that location, the workbook has a Range Name called CED, and the Range Name is in the first Sheet. Otherwise, you have to specify a Sheet name as well, e.g.
{ LINK Excel.SheetMacroEnabled.12 "the full pathname of a .xlsm file" Sheet2!CED \a f 0 \p }
It's just a guess, but if your code is trying to connect to a Workbook where the range defined by CED is not in the first sheet, you would see the error you describe.
Further, the scope of the CED Range Name has to be either "workbook" or the name of the first sheet. Otherwise, if the scope is the first sheet but the range is actually in another sheet, or vice versa, I do not think Word can make the connection whatever subset name you provide (my guess is that Word never really caught up with Excel after Excel introduced multi-sheet workbooks).
If CED can reference sheets other than the first one, I think you will probably have to use the Excel object model to discover which sheet its Range is in, construct the appropriate Subset name, and delete/re-insert the LINK field.

Getting a value from a named range in VBA

I want to retrieve a value from named range. Imagine a named range that has X columns and Y rows. I want to return a value, e.g., from column 2, row 3. The issue I experience is that if I write the code and run it, Excel throws an error. If I write the code into the watch window, it returns fine. See below
...
Dim NamedRange As Variant: NamedRange = Range(NamedRangeName)
...
Dim ReturnValue As Object
Set ReturnValue = NamedRange(RowIndex, ColumnToRetrieveIndex) 'Throws Run-time error 424. Object required
If I write
NamedRange(RowIndex, ColumnToRetrieveIndex)
into the watch window, I can see the correct value of the cell.
I don't know VB much so I guess it's just some kind of syntax error how I want to pass it into the ReturnValue but I just can't figure it out.
Use this
ThisWorkbook.Names("myNamedRange").RefersToRange(1,1)
To get the value from the first cell in the named range "myNamedRange"
With ThisWorkbook.Names you can access all named ranges of all the sheets within the current workbook.
With RefersToRange you get a reference to the actual range.
This looks like a good place to put a handy note (as it's a top search result):
If you name a cell "TopLeft", then Sheets(1).Range("TopLeft").Value gets the contents, and Sheets(1).Range("TopLeft").Offset(2,3).Value gets the value from 2 down, 3 across from there.

Shapes not tied to worksheets?

ok so,
I was hoping to have some stuff in my excel app persist, as in hang around, and I want something a little more reliable than global variables as these are reset when code is edited or if say the app is halted. (which does often happen)
So I've been using shapes, and they work good, but they rely on at least one worksheet always being constant right? as shapes are tied to sheets, if the sheet with the shapes gets deleted the shapes go away. And the users often delete / add new sheets, theres no one sheet that is always a constant, and they wouldnt let me force that on them either.
so is there a way to make shapes tied to workbooks instead of sheets? so then if a sheet is deleted with shapes on it, the shapes wont disapear.
any help or other suggestions appeciated
Edit
Thanks again to: #David Zemens for the answer that got me through, just in case anyone ever looks at this down the road, the code to add a named range is: workbook.Names.Add Name:="Name", RefersTo:="value" - you NEED to add refersto or it will error. you can put in a temp value like "temp" and set the value later, but you have to have refers to when you add
You can use Named Ranges to save string data between sessions. They can be children of the Workbook or of specific Worksheets. You will want the former. From the Formula Ribbon, Names Manager, Define Name like so:
Then, in your VBA, you can retrieve and set this range's value like:
Public Const CSVFileName as String = "sFileName"
Sub YourSubroutine()
Dim nm As Name
Set nm = ActiveWorkbook.Names(CSVFileName)
'Get the value:
MsgBox Replace(Replace(nm.Value, "=", vbNullString), """", vbNullString)
'Set the value:
nm.Value = "C:\documents\filename.CSV"
End Sub
The value associated with the Named Range persists beyond runtime, it is basically a property of the workbook.
I will post this now for you to review. I will try to work up an example of the XML Customer Data, and will revise my answer with that, later.

How to get/set unique id for cell in Excel via VBA

I want to have/define a unique id for each data row in my Excel data sheet - such that I can use it when passing the data onwards and it stays the same when rows are added/deleted above it.
My thoughts are to use the ID attribute of Range (msdn link)
So, I have a user defined function (UDF) which I place in each row that gets/sets the ID as follows:
Dim gNextUniqueId As Integer
Public Function rbGetId(ticker As String)
On Error GoTo rbGetId_Error
Dim currCell As Range
'tried using Application.Caller direct, but gives same error
Set currCell = Range(Application.Caller.Address)
If currCell.id = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.id = Str(gNextUniqueId)
End If
rbGetId = ticker & currCell.id
Exit Function
rbGetId_Error:
rbGetId = "!ERROR:" & Err.Description
End Function
But this fails at the line mentioned with
"Application-defined or object-defined error"
I thought perhaps its one of those limitations of UDFs, but I also get the same error if I try it from code triggered from a ribbon button...
Any other suggestions on how to keep consistent ids - perhaps I should populate the cells via my ribbon button, finding cells without IDs and generating/setting the cell value of those...
EDIT:
As Ant thought, I have the sheet protected, but even in an unlocked cell it still fails. Unprotecting the sheet fixes the problem.... but I have used "Protect UserInterFaceOnly:=True" which should allow me to do this. If I manually allow "Edit Objects" when I protect the sheet it also works, but I don't see a programmatic option for that - and I need to call the Protect function in AutoOpen to enable the UserInterfaceOnly feature...
I guess I need to turn off/on protect around my ID setting - assuming that can be done in a UDF... which it seems it cannot, as that does not work - neither ActiveSheet.unprotect nor ActiveWorkbook.unprotect :(
Thanks in advance.
Chris
Okay...
It does appear that if the sheet is locked, macros do not have write access to low-level information such as ID.
However, I do not think it is possible to unprotect the sheet within a UDF. By design, UDFs are heavily restricted; I think having a cell formula control the sheet protection would break the formula paradigm that a cell formula affects a cell only.
See this page on the Microsoft website for more details.
I think this limits your options. You must either:
give up sheet protection
give up the UDF, use a Worksheet_Change event to capture cell changes and write to ID there
use a UDF that writes the ID into the cell value, rather than save to ID
The UDF approach is fraught with problems as you are trying to use something designed for calculation of a cell to make a permanent mark on the sheet.
Nonetheless, here's an example of a UDF you can use to stamp a "permanent" value onto a cell, which works on unlocked cells of a protected sheet. This one only works for single cells (although it could be adapted for an array formula).
Public Function CellMark()
Dim currCell As Range
Set currCell = Range(Application.Caller.Address)
Dim myId As String
' must be text; using .value will cause the formula to be called again
' and create a circular reference
myId = currCell.Text
If (Trim(myId) = "" Or Trim(myId) = "0") Then
myId = "ID-" & Format(CStr(gNextUniqueId), "00000")
gNextUniqueId = gNextUniqueId + 1
End If
CellMark = myId
End Function
This is quite flawed though. Using copy or the fillbox will, however, retain the previous copied value. Only by explicitly setting cells to be a new formula will it work. But if you enter in the formula into the cell again (just click it, hit ENTER) a new value is calculated - which is standard cell behaviour.
I think the Worksheet_Change event is the way to go, which has much more latitude. Here's a simple example that updates the ID of any cell changes. It could be tailored to your particular scenario. This function would need to be added to every Worksheet the ID setting behaviour is required on.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim currCell As Range
Set currCell = Target.Cells(1, 1)
Dim currId As String
currId = currCell.ID
If Trim(currCell.ID) = "" Then
Target.Parent.Unprotect
currCell.ID = CStr(gNextUniqueId)
Target.Parent.Protect
gNextUniqueId = gNextUniqueId + 1
End If
End Sub
Last note; in all cases, your ID counter will be reset if you re-open the worksheet (at least under the limited details presented in your example).
Hope this helps.
Concur with Ant - your code works fine here on Excel 2003 SP3.
I've also been able to use:
Set currCell = Application.Caller
If Application.Caller.ID = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.ID = Str(gNextUniqueId)
End If
Aha! I think I have it.
I think you're calling this from an array formula, and it only gets called ONCE with the full range. You can't obtain an ID for a range - only a single cell. This explains why Application.Caller.ID fails for you, because Range("A1:B9").ID generates an Application-defined or object-defined error.
When you use Range(Application.Caller.Address) to get the "cell" you just defer this error down to the currCell.ID line.
I think we may have a few issues going on here, but I think they are testing issues, not problems with the code itself. First, if you call the function from anything other than a Cell, like the immediate window, other code, etc. Application.Caller will not be set. This is what is generating your object not found errors. Second, if you copy/paste the cell that has the function, they you will by copy/pasting the ID too. So wherever you paste it to, the output will stay the same. But if you just copy the text (instead of the cell), and then paste then this will work fine. (Including your original use of Application.Caller.)
The problem is with Application.Caller.
Since you are calling it from a user defined function it is going to pass you an error description. Here is the remark in the Help file.
Remarks
This property returns information about how Visual Basic was called, as shown in the following table.
Caller - Return value
A custom function entered in a single cell - A Range object specifying that cell
A custom function that is part of an array formula in a range of cells - A Range object specifying that range of cells
An Auto_Open, Auto_Close, Auto_Activate, or Auto_Deactivate macro - The name of the document as text
A macro set by either the OnDoubleClick or OnEntry property - The name of the chart object identifier or cell reference (if applicable) to which the macro applies
The Macro dialog box (Tools menu), or any caller not described above - The #REF! error value
Since you are calling it from a user defined function, what is happening is Application.Caller is returning a String of an error code to your range variable curCell. It is NOT causing an error which your error handler would pick up. What happens after that is you reference curCell, it's not actually a range anymore. On my machine it tries setting curCell = Range("Error 2023"). Whatever that object is, it might not have an ID attribute anymore and when you try to set it, it's throwing you that object error.
Here's what I would try...
Try removing your error handler and see if VBA throws up any exceptions on Range(Application.Caller.Address). This won't fix it, but it could point you in the right direction.
Either through logic or Application.ActiveCell or however you want to do it, reference the cell directly. For example Range("A1") or Cells(1,1). Application.Caller.Address just doesn't seem like a good option to use.
Try using Option Explicit. This might make the line where you set curCell throw up an error since Range(Application.Caller.Address) doesn't look like it's passing a range back, which is curCell's datatype.
I have found that if I protect the sheet with "Protect DrawingObjects:=False", the UDF can set the Id. Strange.
Thanks for all the help with this.