2

I have a COM add-in for Excel that works with the active workbook, and while it mostly works I'm having trouble with one particular scenario.

If I have a chart in a Word 2010 document that I created using Insert->Chart, then when I click Chart Tools->Design->Edit Data, it opens up the workbook that contains the data for the chart. My add-in can (mostly) work with that workbook.

However, my add-in needs to know the folder in which the workbook is saved, and in this scenario I'd like to use the folder in which the containing document (Word document or PowerPoint presentation) is saved. But, I cannot find a way to get any information about the container document - in fact, I can't even find a way to tell that the workbook I'm dealing with is embedded in a document.

Are there any methods that would give me access to the container document?

Gary McGill
  • 26,400
  • 25
  • 118
  • 202
  • I'm not sure if this is what you are looking for but... to check if `Workbook` (Data) is embedded you could check it this way: `ActiveDocument.InlineShapes(1).Chart.ChartData.IsLinked` which will return `false` if workbook is embedded and `true` if it's not embedded but linked... If it's linked to other Excel file you could then check the file path this way: `ActiveDocument.InlineShapes(1).Chart.ChartData.Workbook.Path` – Kazimierz Jawor Jun 11 '13 at 21:30
  • @KazJaw: sorry, but "ActiveDocument" would only work if I had programmatic access to the container document. That's the point of the question. Because my add-in is an Excel add-in, it can only "see" the workbook. What I'm really hoping for is something like "ThisWorkbook.ContainerDocument" but as far as I can see there's no such thing. – Gary McGill Jun 12 '13 at 12:21
  • Are you trying to identify the `Word` document that contains the chart?? Or, as I have been assuming so far, the path of the embedded `ChartData` (which does not exist)? – David Zemens Jun 12 '13 at 17:44
  • I'm trying to identify the container document, yes. – Gary McGill Jun 12 '13 at 20:53
  • OK. So since the macro/add in "mostly" works on the `ChartData` workbook, how are you doing this? If you are not using the add-in to open the Word file and activate `ChartData` I'm guessing you do these manually (open the Word file, and activate chartdata, which brings it up in Excel, where you can then run your add-in/macros on the `ChartData`). Correct? – David Zemens Jun 13 '13 at 00:28
  • Yes, the add-in (a COM add-in, not a macro) is activated once the user opens Excel, which in this case is done by activating the chart data. – Gary McGill Jun 13 '13 at 08:41
  • @GaryMcGill I think that is the problem. The answer to your question is "No, there does not seem to be any way to do this." I suspect that any reason the macro *needs* the `.Path` would be moot, anyways, so even if you were able to obtain the path (If i knew *why* your add-in needs this information I could maybe suggest more), it's ultimately not going to do what you think it is going to do. If you are trying to do a `.Save` on the .DOC file, this would easily be achieved by modifying the add-in to take control of this file and open it programmatically, instead of user/manually. – David Zemens Jun 14 '13 at 01:42
  • @DavidZemens: "it's ultimately not going to do what you think it is going to do". I'm not an idiot - I know exactly what I want to do with the path, thanks, and I also know that re-posing the problem in order to answer a different question is not helpful, which is why I don't want to get into it. If your answer is "there's no way" (or rather "I don't know how"), then that's fine, let's leave it there. I can think of at least one (ugly) way to do it which would suffice, and I'll post that as an answer once I get it to work. – Gary McGill Jun 14 '13 at 08:42
  • Again if I had any idea why you need the path I could probably be more helpful. This is a very intersting question and I'm anxioufs to see what you come up with. There's an ugly way but if you're passing this to the COM add in then I don't understand why you wouldn't be open to a more robust solution -- if you're going to edit the com add in to get this path you might as well do it the best possible way, not the ugly way, right? – David Zemens Jun 14 '13 at 11:58

2 Answers2

1

Let's give this another shot...

Create another add-in (I will give an example of an XLAM) then or you could probably modify your COM add-in to this, or to ensure that this new add-in is running. This add in will trap the Application-level App_WorkbookActivate event, and check the new workbook name to see if it likely comes from Microsoft Word.

If the workbook meets this criteria, since it is triggering the App_WorkbookActivate event, then it seems reasonably safe to assume that the current ActiveDocument in MS Word is the container.

Then, we just set some object variables to capture the Word.Application and then just a matter of getting the ActiveDocument, it's .Path and it's .Name, which we can store these values in Named variables within the Excel workbook.

You can view them in the Names manager, or access them programmatically by reference to their name, .Names("docPath") and .Names("docName").

Named Variable with Container .DOC path & name

Put this in a standard module in the XLAM file:

Sub Auto_Open()
    Application.Run "ThisWorkbook.Workbook_Open" 'just in case
End Sub

The following code goes in the ThisWorkbook module of the XLAM file:

Option Explicit
Private WithEvents App As Application
Dim dictWorkbooks As Object
Private Sub Workbook_Open()
    '## Instantiate the public variables for this Add-in:
    Set App = Application
    Set dictWorkbooks = CreateObject("Scripting.Dictionary")
End Sub

Private Sub App_WorkbookActivate(ByVal Wb As Workbook)
    '## Attempt to determine if a Workbook is opened from MS Word,
    '   and if so, store the container document's path & name in
    '   named variable/range in the Workbook.
    Dim wdApp As Object
    Dim wdDoc As Object
    Dim docPath As String
    Dim docName As String
    Dim w As Integer

    If Wb.Name Like "Chart in Microsoft Word" Then
        'Get out of here if we already have this workbook activated.
        If dictWorkbooks.Exists(Wb.Name) Then Exit Sub
        Set wdApp = GetObject(, "Word.Application")
        Set wdDoc = wdApp.ActiveDocument
        docPath = wdDoc.Path
        docName = wdDoc.Name
        dictWorkbooks.Add Wb.Name, docName

        With Wb
            On Error Resume Next
            .Names("docPath").Delete
            .Names("docName").Delete
            On Error GoTo 0
            .Names.Add Name:="docPath", RefersToR1C1:=docPath
            .Names("docPath").Comment = "A variable stores the parent DOC file's path"
            .Names.Add Name:="docName", RefersToR1C1:=docName
            .Names("docName").Comment = "A variable stores the parent DOC file's name"
        End With
    End If

End Sub
Private Sub App_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
    '## If you open multiple charts from the word document, without closing the Workbook
    ' they will be assigned unique names. However, if you open & close multiple Workbook
    ' they will all have the same name "Chart in Microsoft Word".  This method will
    ' remove an existing Key from our Dictionary when a workbook is closed, in order to
    ' prevent false matches.
    If dictWorkbooks.Exists(Wb.Name) Then _
        dictWorkbooks.Remove Wb.Name
End Sub
David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • Thanks, yes - using GetObject (or equivalent) and then ActiveDocument (or ActivePresentation) is what I was keeping as an ugly fallback. – Gary McGill Jun 17 '13 at 21:15
  • No problem. I'm pretty sure I left no stone unturned, but just couldn't figure out any way -- natively -- to get the info that you're trying to get. This seems like a pretty safe hack. My guess is that The "Chart in Other Application" simply wasn't designed to be used the same way as charts in standalone Excel workbooks and so that's a discrepancy. Seems like an odd oversight, but then again MS has been known to leave *actual users* out of the discussion for their development.... If you do figure out some other method, would love to know about it. Cheers. – David Zemens Jun 18 '13 at 03:23
  • 1
    Well, actually, my plan is to start with the `ActiveDocument`, loop through all charts in the document, and examine the `ChartData` property. I can ignore those where `IsLinked` is false. Then I'll look at the `ChartData.Workbook` property; that'll raise an error if the chart data isn't activated. If there's no error, then I can test `ChartData.Workbook.Name` and match that against the actual workbook name - which will be "Chart 2 in..." etc. (If I don't find it in the active document, I can try other documents too). I think that's safer, and removes the need to maintain "state". – Gary McGill Jun 18 '13 at 08:37
0

I'm pretty sure that a chart created via the Insert | Chart ribbon is not a linked document, and as such, it doesn't have a .Path property. You shouldn't need to have a path, you can access the workbook and the chart object directly:

To access the ChartData workbook/worksheet object, you can do something like this:

Sub OpenChartData(shp as InlineShape)


Dim shp As InlineShape
Dim cht As Chart
Dim wb As ChartData

    If shp.Type = wdInlineShapeChart Then
        Set wb = shp.Chart.ChartData
        Set cht = shp.Chart  '## In case you need to manipulate the Chart options like Title, Axes, etc... you can use this variable.
        wb.Activate  '## Activate the chart's ChartData sheet

        ' do stuff to the worksheet

        wb.Workbook.Application.WindowState = -4140  '## Hide the ChartData when you're finished.
    End If

End Sub

Update from Comments:

The chart data is either a linked workbook, or it is not. If it is not a linked workbook, then it does not exist somewhere as a "saved" file with a full path, etc.

To Answer Your Question:

Are there any methods that would give me access to the container document?

You have access to the container document via the ChartData object.

However, you do not want to use this object, because you insist, or erroneously understand that the workbook is "saved" somewhere, and it is merely a question of identifying where this workbook is saved, in order for your macro/add-in to work without any modifications.

Your assumption is incorrect. The workbook is not saved somewhere, it is entirely encapsulated in the ChartData object, which is part of the Microsoft Word Object Model.

From MSDN

A new object, ChartData, has been added to the VBA object models for Word to provide access to the underlying linked or embedded data for a chart. Each chart has, associated with it, the data used to draw the chart in Word. The chart data can either be linked from an external Excel workbook, or embedded as part of the chart itself. The ChartData object encapsulates access to the data for a given chart in Word. For example, the following VBA code example displays, then minimizes, the chart data for each chart contained by the active document in Word.

http://msdn.microsoft.com/en-us/library/office/ff821389.aspx

So, your options are:

  1. Re-work your macro to be compatible with the ChartData object, or
  2. Create your charts in Excel and insert as OLEObjects/paste in to DOC, instead of using Word's Insert | Chart.
  3. Attempt to convert all charts created perviously using Word Insert | Chart to OLEObjects.

Further reading:

Update Chart Data

Creating Charts with VBA

David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • It's not a *linked* Excel document, but when you edit the data, it does open up in Excel; I'm pretty sure that it *is* a fully-fledged Excel workbook. (The chart, by contrast, is *not* an Excel chart). The workbook does have a Path property - it's just blank, in much the same way as for an as-yet-unsaved workbook. Anyway, accessing the chart from the document as per your example above is of no help in my situation, because my Excel add-in doesn't know anything about the document - all it can see is the workbook. That was the crux of my question. – Gary McGill Jun 12 '13 at 12:17
  • what do you mean "All it can see is the workbook"? If you can see the workbook, then you can use all of the standard Excel object model to manipulate the data in that workbook. I guess maybe clarify your question: what are you trying to *do* to the Workbook, which your macro is unable to do? If you can post your macro's code, perhaps we can figure out *why* it's not working, and suggest way(s) to fix it. – David Zemens Jun 12 '13 at 12:52
  • From my question: "my add-in needs to know the folder in which the workbook is saved, and in this scenario I'd like to use the folder in which the document is saved". I'm not open to finding a way to rewrite my add-in to remove this requirement, so I don't want to get distracted from the main point of the question. Let's say for the sake of argument that it simply needs to record the folder somewhere (it doesn't, but as far as I can see that makes no difference). – Gary McGill Jun 12 '13 at 16:35
  • The problem is that this document is not "saved" in the sense that your Add-in presently requires. The `ChartData` worksheet is zipped as part of the open xml DOCX/DOCM file format, and is *part of* the Word Document, not some external Excel file which is "linked" to your DOC file. I am fairly certain there is no way around this without making some modifications to your add-in, either **A)** modify the add-in so that it recognizes the `ChartData` workbook, or **B)** the much nastier proposition converting all charts to OLEObjects with external linked XLSX data. Good luck. – David Zemens Jun 12 '13 at 16:55
  • see revision for further detail. ] – David Zemens Jun 12 '13 at 17:16
  • Again, my add-in is an **Excel** add-in. There is no ChartData object, because you only have that in the context of the container document, and it's the container document I'm trying to get to. I need to start with the Workbook, and somehow get the Document or Presentation that contains it. – Gary McGill Jun 12 '13 at 20:55
  • I understand this is an Excel add-in. Will comment on OP in case others can help based on this, too. – David Zemens Jun 13 '13 at 00:24