I'm trying to modify an embedded excel table in a word document programmatically. To do this, I have modified the docx file and the embedded excel file.
The significant part of the main document is the following:
<w:object w:dxaOrig="8406" w:dyaOrig="2056">
<v:shape id="_x0000_i1028" type="#_x0000_t75"
style="width:390.75pt;height:95.25pt" o:ole=""><v:imagedata r:id="rId14"
o:title=""/>
</v:shape>
<o:OLEObject Type="Embed" ProgID="Excel.Sheet.12" ShapeID="_x0000_i1028"
DrawAspect="Content" ObjectID="_1349794876" r:id="rId15"
UpdateMode="Always"/>
</w:object>
The word document uses an OLEObject to link to the embedded excel document. For displaying purposes,
a .wmf file is used (using the v:shape element). I have modified the excel document, which outdated this preview.
This results in some strange behaviour in the document:
The preview of the embedded (excel) table shows the wrong data
Double clicking on the embedded table opens the table in an embedded excel and shows the correct data
Closing the embedded editor triggers the generation of a new preview, showing the correct data
Of course, I want the table to show the correct table when the document is opened. How can I trigger Word to discard the image and redraw the preview?
An ideal solution for me would be to trigger the regeneration of the preview just by modifying the contents of the docx, but solutions using a small script would also help.
There is no perfect solution to this, but one that works most of the time is to force an open/close of the OLEFormat.Object. It doesn't matter if you are rehydrating the embedded Excel worksheet from outside of Word (i.e. manipulating the Open XML format) or doing it through the object model. What it involves is opening the embedded Excel spreadsheet from within Word and then closing that object for the image to be changed to the current values in the embedded spreadsheet and the new image to be created.
It depends just a bit if you are doing this on many Word documents or just one. In the former case, a global template (such as normal.dot or a custom one you create and put in the STARTUP folder) or in the later case, just run code behind of one document. Both have a different way to getting things to run, but essentially you will be hooking the Document_Open event and from there checking if the current document has OLE Embedded objects and if so, opening and closing them.
Without going into the hook, like I said, the code isn't pretty. Basically because it uses SendKeys. Most of the time, this will work. Some of the time, it won't. That is the nature of SendKeys and other programs receiving the focus without consent (such as an instant messenger program).
If Word has the focus (which means you can't open the VBE and press F5), this code should do the trick:
Sub UpdateEmbeddedXLSX()
Dim workbook As InlineShape
For Each workbook In ActiveDocument.InlineShapes
With workbook
If .Type = wdInlineShapeEmbeddedOLEObject Then
''# Excel.Sheet.12 for Excel 2007
If .OLEFormat.ClassType = "Excel.Sheet.12" Then
''# Open Object as spreadsheet
.OLEFormat.DoVerb wdOLEVerbPrimary
''# If you want, you can also do any updates here like
.OLEFormat.Object.ActiveSheet.Cells(2, 2).Value = ".1"
''# Nasty - but it works - SendKeys
SendKeys "{ESC}", True
End If
End If
End With
Next
End Sub
At the very least, you could put this code in your normal.dot and assign it to the QAT to be run as a macro.
Note that the code doesn't get around Excel opening, the values changing and then closing - that is part and parcel of using embedded objects. Using linking instead of embedding would be a much smoother way to do all of this, but I realize it's not always an option.
Just to add to an old post in the event someone stumbles upon this like I did:
The above code works great, but I modified it to use bookmarks instead of using SendKeys. The SendKeys statement really messes with the NumLock on my keyboard. Just one of the quirks of that command.
What I did was create bookmarks in my Word Doc Template. Then in my code, I created a pointer to the bookmark:
Dim bMark as bookmark
Set bMark as ActiveDocument.Bookmarks("NameOfBookmark")
Then in place of the SendKeys statement, I did the following:
bMark.Range.Select
Selection.EndKey
This basically pulled the focus out of the embedded worksheet, and onto the bookmark of the page. Then the .EndKey statement simply removed the selection. You don't really even need it.
Hope this helps!
If anyone's coming here looking for how to update an Excel Object embedded in a Visio document via VBA macros without double-clicking, you just need to call Save on the embedded workbook.
Dim wb As Excel.Workbook
Set wb = ThisDocument.Pages("MyPageName").Shapes("NameOfWorksheetShape").Object
'Calling me updates the visible worksheets.
wb.Save
Related
I run a lot of macros, and lately had to change to O365, which gave me the "wonderful" 64 bit Excel. One of its most maddening shenanigans is that when I run some macros (working with SAP GUI, creating SAP reports, naming them, saving and downloading to a specific folder where the next macro step opens them and incorporates into a relevant macro sheet), the new Excel opens these in a new instance (which I don't want but cannot prevent - can you help here?), which causes a dialog "File in Use" (which I don't want but cannot prevent - can anyone help me here?) because it tries to open my PERSONAL.XLSB (which I need and therefore don't want to get rid of just because of this) and qualifies it as "locked for editing" by another user, who is myself (which is absolutely stupid but I cannot change it - can anyone help me perhaps with this part?).
Sometimes the macro finishes fine when I don't answer that dialog; sometimes it seems to cause the Excel to hang up in trying to do next steps, sometimes the macro quits on me when I click "Read only" and so on.
All this is maddening also because these files that (possibly) SAP GUI opens, I have to waste my time by closing them after the macro finishes (has anyone a possible solution in stopping SAP opening these files? I was unable to find it anywhere so far.).
In some macros I fixed it by a specific part of code which waits till when these files open up and then it closes them - yet even that wait is wasting my time and it would be better without.
But with O365 and other instances it is even more colorful, because some files open in the same instance (this issue was there even before the O365 though) and I can manually close them after the macro finishes (although again, I have to wait till they open, which is again "vanity and torture of the soul"), but those opening in the new instance (specific to O365) show as blank sheets, I have to click into them, a warning sound follows but no dialog is visible, then I have to click into them again from the Excel icon in the bottom menu bar and then it tells me that I cannot close them because a dialog is open.
Then I need to close this dialog plus the one which appeared about the "File in Use", then I finally wait for the file (that I don't need) to open (because it is already saved inside the folder and already copied and pasted onto the relevant macro sheet) and only then I can close it.
So far I was unable to find anywhere on the internet an advice for how to stop SAP opening these files (when they are already saved where I need them), neither how to stop them opening in new Excel instances. So reluctantly, I think I should aim for at least a tiny little VBA code which will cause my macro to click on the "Read Only" button. Can anyone here help me with any part of this "Excel complaint"?
I do not know how/if SAP can be 'persuaded' to not open the workbooks in discussion... But, I think, you can handle that in a different way the "PERSONAL.XLSB" behavior, from the opening point of view. Of course, if SAP itself has this "bad habit" and not your code opens it in a new session, which have to be solved in a different way...
"PERSONAL.XLSB" is located in "...AppData\Roaming\Microsoft\Excel\XLSTART" folder. All workbooks located in this folder are automatically open when an Excel session starts;
You may change the "PERSONAL.XLSB" workbook path. If you want it to be open when a specific (necessary) workbook needs it, add a reference to it.
addRef. In order to do that, modify the (standard) VBAProject (of "PERSONAL.XLSB"), in, let us say, "VBPersProj", then (being in VBE) trough 'Tools - References...` tick its check box and press 'OK';
If the exported workbook is open in a new session and, even if it does not bother you any more because of the annoying dialogs and you need to identify that specific session, you can use the next function:
Function sameExSession(wbFullName As String, Optional boolClose As Boolean) As Boolean
Dim sessEx As Excel.Application, wb As Workbook
Set sessEx = GetObject(wbFullName).Application
If sessEx.hWnd = Application.hWnd Then
sameExSession = True
Else
sameExSession = False
If boolClose Then
sessEx.Workbooks(Right(wbFullName, Len(wbFullName) - InStrRev(wbFullName, "\"))).Close False
sessEx.Quit: Set sessEx = Nothing
End If
End If
End Function
It offers the possibility to close the workbook and the session itself if it is a different against the one where the code runs. It is an Optional parameter to be used, only if you need that. You can test it in the next way:
Sub testSameExSession()
Dim wbFullName As String
wbFullName = ThisWorkbook.fullName 'use here the full name of the SAP exported workbook
If sameExSession(wbFullName, True) Then
'process the workbook if this is what you need
End If
'if not open in the active session, the workbook could be closed and session quit, choosing the above way
'You can also simply call the function, without the closing parameter, as:
Debug.Print sameExSession(wbFullName)
End Sub
In case of False function retur, it can be closed and reopen in the existing session, if needed...
Edited:
If you need personal.xlsb being used/open when ribbon controls calls some of its Subs/functions, please adapt the code to call it in the next way:
Dim x as Long
x = Run("'C:\Users\your_UserName\AppData\Roaming\Microsoft\Excel\XLSTART\PERSONAL.XLSB'!GiveMeFive", 4, 3)
if calling a function (with parameters), which must return. Or
Run "'C:\Users\your_UserName\AppData\Roaming\Microsoft\Excel\XLSTART\PERSONAL.XLSB'!YourSubName"
when calling a Sub. In such a way, "Personal.xlsb" is opened if it is closed. And, in your specific case nothing will press the ribbon controls.
But, isn't the ribbon added when a specific workbook is open? Probably, the one you are using to make the above mentioned calls... If so, adding the suggested reference at "Personal.xlsb", will configure the new ribbon tab only when it is loaded. And your mentioned "problem" does not exist, in fact. Is it an add-in?
I really don't know what causes error 400.
Below code runs perfectly fine in normal mode but as soon as i enable my excel in sharing mode and tries to user form, it gives me VBA 400.
What i am trying to do here is to change shape's text and disable its OnAction event, once user form is shown to user. so that another user accessing same file will come to know that someone is using "User Form" to enter data.
Dim shp As Shape
For Each shp In ActiveSheet.Shapes
If shp.TextEffect.Text = "Sort Customer" Then
shp.OnAction = ""
shp.TextEffect.Text = "Wait!!!"
End If
Next
Q. Is there any way to publish changes made by any user in shared excel automatically.
I suspect that your code falls in one of the numerous limitations of Excel shared mode, described here (see unsupported features), including
Using a data form to add new data
Using drawing tools
Inserting or changing pictures or other objects
(Please note that, due to its format, I could not easily copy that list of unsupported features in my answer.)
As far as I know, in order to keep the changes you should choose if the first one who introduces the data rules or you will choose in case of conflict. As you are looking for an "automatic" way, you should chose the first one.
You can find a good explanation described here
At Review > Share Workbook , Advanced Tab. At "Conflicting changes between users", you should chose "The changes being saved win". So as the data are introduced and saved, they are reflected.
Hope it helps.
Create a vba function in the sheet (NOT A MODULE) where users can activate the user form:
insert the following function there:
Function HyperlinkClick()
'source: https://stackoverflow.com/a/33114213/11971785
Set HyperlinkClick = Range("B2")
If HyperlinkClick.Value = "Sort Customer" Then
'sets info on WAIT
HyperlinkClick.Value = "WAIT!!!"
'shows userform
UserForm1.Show
Else
'sets info back to normal value
HyperlinkClick.Value = "Sort Customer"
End If
End Function
In the user form you can add an userform_terminate Event, which automatically changes the value in B2 back (I guess you could also do that for an workbook Close Event be on the safe side).
Private Sub userform_terminate()
'Code goes here
Range("B2").Value = "Sort Customer"
End Sub
In Excel now create a "Frontend" such as this:
and add the formula:
=HYPERLINK("#HyperlinkClick()";"Click")
to the cell where a user needs to click to open the UserForm (in this case to D2).
If you now share the workbook and click on "Click" in D2 an Event is triggered and the VBA Function "HyperlinkClick()" is called. In this function you can essentially do anything now.
Explaination:
Instead of using a graphic, button etc. which will not work correctly in shared mode, we can simply use links (which work) to trigger an Event.
Instead of "creating" and "deleting" Hyperlinks (which also does not work in shared mode) we simply build dynamic links which Point to userform.show or to nothing, depending of the situation.
Error 400 Problem: Should be solved by skipping the modify object part of the code.
Multiple User Problem: Should be solved, since only one user can activate the userform.
Is there any way to publish changes made by any user in shared excel automatically.: I guess so, please provide more information on what exactly you want to achive (incl. example).
Tip:
In General you might want to check out MS Access since it has as default feature multi-user Access and users there can use the same form at the same time, since the users only get exclusive Access for specific datapoints not the whole table/workbook or file.
I want to create an excel template that will includes formulas for dating the columns. However, since those formulas will be based on TODAY(), I need to convert them to static strings (so the dates don't change everytime someone opens it). Is there a way to add a macro that will run automatically when someone creates a new spreadsheet based on the template? (Similarly to Auto_Open(), only on Create, rather than Open). If so, I could just create a macro that will replace the formulas with their results upon document creation. Can this be done?
[Note: I'm not married to this solution; it just seemed like the simplest way to protect my spreadsheet. If someone can suggest an alternative approach, I'd be obliged.]
I have a couple thoughts...
If you run a copy/paste values macro every time it really won't matter, right?
You could check if the file exists yet (has been saved), and if not
then this must be the template opened as a new workbook, maybe?
Code:
Private Sub Workbook_Open()
If Dir(ActiveWorkbook.Name) = "" Then
'run the macro?
MsgBox "I'm gonna run this macro"
End If
End Sub
You could have a cell on one of the sheets, that will never be used,
or is hidden, that will store whether or not to run the macro, and
change that when the file is opened or when the macro is ran. Then
have a macro run on open that checks that cell. (Or custom/document property)
You could populate the cells that have the today() formula only on
open and if they are already populated then don't run the macro?
I realized that there is no need for a Workbook_Create() function, as that behavior can be emulated by simply deleting the macro after it has run once (which happens when it is first created). Deleting macros is done automatically when the file is saved with a .xlsx extension. In addition, you need to prevent the macro from running when you open the template itself (while editing the it). You can hold the SHIFT key when opening it to prevent auto-run macros, but since that method isn't foolproof, I added this code at the top:
Private Sub Workbook_Open()
'If we're opening the template, don't run the macro
If Application.ActiveWorkbook.FileFormat = xlOpenXMLTemplateMacroEnabled Then
Exit Sub
End If
...
'add code here to SaveAs .xlsx, thus removing the macros, so it won't run every time.
End Sub
(Note: I didn't show my SaveAs code as it is rather messy: I wanted to suppress the default warning about losing macros, but wanted to also protect the user from inadvertantly overwriting previous file. If anyone is interested, I could post it)
I need a way to programatically launch the macro-recorder in Excel, and supply the name for the new macro that will get created.
This can be from VSTO or VBA, or using the Office interop assemblies.
Any ideas how this can be accomplished?
In VBA:
Dim ctrlStart As CommandBarControl, ctrlStop As CommandBarControl
Set ctrlStart = Application.CommandBars.FindControl(ID:=184)
Set ctrlStop = Application.CommandBars.FindControl(ID:=2186)
ctrlStart.Execute
'name part would go here, but first you have to deal with a modal dialog
ctrlStop.Execute
It looks like the Execute method on the RecordMacro control opens a modal dialog. There is no way to feed a parameter to this, or to do anything like SendKeys. The only way I see to do it is to write a sub that will rename the macro after the fact. It will be a little complicated to determine what the name of the new macro is, and you will still have a dialog box to deal with.
Actually, I'm not a beginner. I know nothing about them, though I do have some programming background.
This is the thing: I have a couple of Word documents and an Excel spreadsheets. The documents need to grab some data from the spreadsheet and then print. There needs to be one document per spreadsheet row, and they need to be printed all together.
I'm not asking for code or anything; I just want to know what's the right tool for the job, and if someone could point me to a tutorial or reference or something.
This is for Office 2003 (or XP, I'm not sure).
EDIT: It seems like there are many ways to do this, so it'd be great if someone listed the pros and cons of each solution. Keep in mind that it's something that will be done many times, and once programmed/recorded/whatever it should be easy to use for someone who is not a programmer.
Javier,
Couldn't find a good tutorial, but something like this should help you get going:
You can enable the developer toolbar, if it's not available from Word options. Then, click on the Visual Basic button and add a procedure or function that can be called from your document or a command button in the Word UI.
Sample showing some super basic Excel integration:
Public Function GetValue()
Dim myExcel As Excel.Application
Dim myWorkbook As Excel.Workbook
Dim myWorkSheet As Excel.Worksheet
Set myExcel = CreateObject("Excel.Application")
Set myWorkbook = myExcel.Workbooks.Open("c:\temp\myworkbook.xlsx")
Set myWorkSheet = myWorkbook.Worksheets(1)
Dim cellValue As String
cellValue = myWorkSheet.Cells(1, 1).Value
GetValue = cellValue
End Function
This will require you add a reference to Excel object library (type library) from the Excel developer IDE.
You can load your Excel spreadsheets via VBA in an ADODB and read the ADODB row by row.
How To Use ADO with Excel Data from Visual Basic or VBA
You can accomplish this with the built-in mail merge facility in Word. There's a walkthrough of how to use it in Word 2003 here
edit: further to the question in the comments, once you have set up the mail merge document, you can save it complete with its link to the data source. This means that when the document is opened again the user just needs to say "Yes" to the choice of data being merged.
The user can (independently) also choose to have the mail merge toolbar displayed. Clicking on the "Merge to new document" button on the Mail Merge toolbar would cause the merged letters to be generated. If the toolbar isn't displayed then they need to go Tools > Letters and Mailings > Mail Merge and use the wizard to complete the job