none
Looking for Alternative to Windows API Timer as work around for the broken "CC Before Store Update Event" RRS feed

  • Question

  • "Broken" may be too strong of a word, but the Run-time error 6197 that occurs anytime you try to interact with a document object when in the Document_ContentControlBeforeStoreUpdate event makes, in my opinion, that event practically useless.

    For example,  if I have a mapped CC dropdown "Conditions" then I might want to set the value of a plain text CC "Conditional Text" based on the value selected in the dropdown.  The code is simplicity itself:

    Private Sub Document_ContentControlBeforeStoreUpdate(ByVal CC As ContentControl, Content As String) Select Case CC.Title Case "Condition"

    Select Case CC.Range.Text
    Case "A"

    ActiveDocument.SelectContentControlsByTitle("Conditional Text").Item(1).Range.Text = "AAA" Case "B"

    ActiveDocument.SelectContentControlsByTitle("Conditional Text").Item(1).Range.Text = "ZZZ"

    End Select

    Case Else 'Do Nothing End Select lbl_Exit: Exit Sub End Sub

    However, when that code is executed it raises:

    Run-time Error: 6197 Descripition: This object model command is not available while in the current event.

    I have discovered that if I call a Windows API timer procedure in the event, then the event will run to completion and then after the time interval I can run other code to interact with the document.  Doing so, I've manage to get my project working so with the workaround the event is not completely useless ;-)

    This is the code in the ThisDocument Module:

    Private Sub Document_ContentControlBeforeStoreUpdate(ByVal CC As ContentControl, Content As String)
      Select Case CC.Title
        Case "Condition"
          'Start a Windows API timer and complete this event.  I'm passing the CC as an argument to the timer procedure.
          mod_Main.StartTimer CC
        Case Else
          'Do Nothing
      End Select
    lbl_Exit:
      Exit Sub
    
    End Sub
    


    This is the code in a Standard module the contains the Windows APIs, related timer procedures and my procedure to interact with the document:

    Option Explicit
    'APIs and associated timer procedures adapted from code published by Chip Pearson:
    'http://www.cpearson.com/excel/OnTime.aspx
    Public Declare Function SetTimer Lib "user32" (ByVal HWnd As Long, ByVal nIDEvent As Long, _
                                                   ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
    Public Declare Function KillTimer Lib "user32" (ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
    
    Public lngTimerID As Long
    Public sngTimeInterval As Single
    Dim m_oCC As ContentControl
    
    Sub StartTimer(ByRef oCC As ContentControl)
      Set m_oCC = oCC
      sngTimeInterval = 0.1
      lngTimerID = SetTimer(0&, 0&, sngTimeInterval * 1000&, AddressOf UpdateConditionalText)
    End Sub
    
    Sub EndTimer()
      On Error Resume Next
      KillTimer 0&, lngTimerID
    End Sub
    
    Sub UpdateConditionalText(ByVal HWnd As Long, ByVal uMsg As Long, ByVal nIDEvent As Long, ByVal dwTimer As Long)
    Dim oCC As ContentControl
      mod_Main.EndTimer
      Select Case m_oCC.Title
        Case "Condition"
          Set oCC = ActiveDocument.SelectContentControlsByTitle("Conditional Output").Item(1)
          oCC.LockContents = False
          Select Case m_oCC.Range.Text
            Case "Condition A"
              oCC.XMLMapping.SetMapping "/ns0:basic_XML[1]/ns0:Condition_A[1]"
            Case "Condition B"
              oCC.XMLMapping.SetMapping "/ns0:basic_XML[1]/ns0:Condition_B[1]"
            Case "Condition C"
              oCC.XMLMapping.SetMapping "/ns0:basic_XML[1]/ns0:Condition_C[1]"
            Case Else
              With oCC
                .XMLMapping.Delete
                .Range.InlineShapes(1).Delete
              End With
          End Select
          oCC.LockContents = True
        Case Else
          'Do nothing.
      End Select
    End Sub
    
    
    
      

    Basically what I have done is map a dropdown CC (Condition) to to

    a customXMLNode and stored three different images in three other XML nodes.  The picture CC ("Conditional Output") is mapped to the appropriate node when the user makes a selection.

    I don't see a way to attach my demo file to this post or I would.

    The problem is that the Windows API timer and my procedure is "fickle."  Any error that may occur in the code (none does now) is a fatal error causing Word to crash and close.  What I would like to find, is an alternative to the Windows API timer.  Something that is not so fickle.  Unfortunately "Application.OnTime" won't work in the event as it too generates the RTE.  Appreaciate any suggestions.  Thanks.


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Saturday, August 24, 2013 2:39 PM

Answers

  • Hi Greg

    Since you're already mapping to a custom XML part why not write the desired content to the appropriate node in the custom XML part, so that it will show up in the content control? I seem to recall that's supported?


    Cindy Meister, VSTO/Word MVP, my blog

    • Marked as answer by Greg Maxey Sunday, August 25, 2013 1:53 PM
    Sunday, August 25, 2013 8:10 AM
    Moderator
  • Cindy,

    Not sure that I did exactly what you are suggesting, but I did something that certainly works and avoids a timer completely.  My XMLPart now contains 11 nodes.

    A node for the dropdown CC "Condition" the dropdown CC is mapped to this node.

    A node for the picture CC "Conditional Image" the picture CC is mapped to this node.

    A node for the text CC "Conditional Text" the text CC is mapped to this node.

    8 nodes with condition content (3 images + 1 no image + 3 text + 1 no text)

    Then, as I think you suggest, I pass the CC to the following procedure using the BeforeStoreUpdate event:

    Sub UpdateConditionalText(ByRef oCC As ContentControl)
    Dim oXMLPart As CustomXMLPart
      Select Case oCC.Title
        Case "Condition"
          Set oXMLPart = ActiveDocument.CustomXMLParts.SelectByNamespace("http://AddIn Basic XML Script/Basic Nodes").Item(1)
          Beep
          Select Case oCC.Range.Text
            Case "Condition A"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_A[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_A_Text[1]").Text
            Case "Condition B"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_B[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_B_Text[1]").Text
            Case "Condition C"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_C[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_C_Text[1]").Text
            Case Else
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:No_Image[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:No_Text[1]").Text
          End Select
        Case Else
          'Do nothing.
      End Select
    lbl_Exit:
      Set oXMLPart = Nothing
      Exit Sub
    End Sub

    Basically just set the values of the nodes mapped to the Conditional Image and Text CCs equal to the appropriate condition nodes.

    Seems to work perfectly.  Thanks for you interest and suggestion.


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Sunday, August 25, 2013 1:53 PM

All replies

  • Hi Greg

    Since you're already mapping to a custom XML part why not write the desired content to the appropriate node in the custom XML part, so that it will show up in the content control? I seem to recall that's supported?


    Cindy Meister, VSTO/Word MVP, my blog

    • Marked as answer by Greg Maxey Sunday, August 25, 2013 1:53 PM
    Sunday, August 25, 2013 8:10 AM
    Moderator
  • Cindy,

    Not sure that I did exactly what you are suggesting, but I did something that certainly works and avoids a timer completely.  My XMLPart now contains 11 nodes.

    A node for the dropdown CC "Condition" the dropdown CC is mapped to this node.

    A node for the picture CC "Conditional Image" the picture CC is mapped to this node.

    A node for the text CC "Conditional Text" the text CC is mapped to this node.

    8 nodes with condition content (3 images + 1 no image + 3 text + 1 no text)

    Then, as I think you suggest, I pass the CC to the following procedure using the BeforeStoreUpdate event:

    Sub UpdateConditionalText(ByRef oCC As ContentControl)
    Dim oXMLPart As CustomXMLPart
      Select Case oCC.Title
        Case "Condition"
          Set oXMLPart = ActiveDocument.CustomXMLParts.SelectByNamespace("http://AddIn Basic XML Script/Basic Nodes").Item(1)
          Beep
          Select Case oCC.Range.Text
            Case "Condition A"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_A[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_A_Text[1]").Text
            Case "Condition B"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_B[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_B_Text[1]").Text
            Case "Condition C"
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_C[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Condition_C_Text[1]").Text
            Case Else
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Image[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:No_Image[1]").Text
              oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:Conditional_Text[1]").Text = _
                       oXMLPart.SelectSingleNode("/ns0:basic_XML[1]/ns0:No_Text[1]").Text
          End Select
        Case Else
          'Do nothing.
      End Select
    lbl_Exit:
      Set oXMLPart = Nothing
      Exit Sub
    End Sub

    Basically just set the values of the nodes mapped to the Conditional Image and Text CCs equal to the appropriate condition nodes.

    Seems to work perfectly.  Thanks for you interest and suggestion.


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Sunday, August 25, 2013 1:53 PM
  • Hi Greg

    Well, that's not exactly what I was suggesting - it's not an approach I was thinking about - but it's definitely a good one.

    Just for the sake of the discussion: My thought was to write the data to the already targeted node. I was aware, as I made the suggestion, that it would be tricky when pictures are involved (base-64). So your way is definitely "simpler", as long as you're dealing with a static set of conditional choices.

    Continuing the intellectual brain-storming, I have to wonder whether it would also be possible to store everything as BuildingBlock entries, linked to a BB content control, and change that assignment? Or whether that approach is also blocked when in an event?


    Cindy Meister, VSTO/Word MVP, my blog

    Monday, August 26, 2013 8:48 AM
    Moderator
  • Cindy,

    Regardless, your suggestion got me unstuck and thinking in a different way.  Thanks.

    Regarding the BB route.  If something like this is what your mean:

    ActiveDocument.SelectContentControlsByTitle("BB Control").Item(1).DropdownListEntries(1).Select

    then unfortunately that generates the same RTE.


    Greg Maxey Please visit my website at: http://gregmaxey.mvps.org/word_tips.htm

    Monday, August 26, 2013 1:10 PM