Solved

Closing .PDF in Acrobat from VB.Net console app without closing the Acrobat application.

Posted on 2004-09-10
9
2,194 Views
Last Modified: 2009-01-07
I posted a question about opening .PDF's from .Net yesterday, and it is working very well.  As a follow-up to that question, I would also like to be able to close a .PDF file opened in Acrobat from a .Net application without having to close the Acrobat window.  Is this possible?  One person mentioned something about OLE objects, but I'm not very familiar with them, so I may need some assistance if that is the answer.

Again, thanks to all for your help.  It is very much appreciated.
0
Comment
Question by:rrouse
  • 3
  • 3
  • 2
  • +1
9 Comments
 
LVL 4

Expert Comment

by:Javert93
ID: 12031311
You can use DDE to control the acrobat reader process. The available commands are list in http://partners.adobe.com/asn/acrobat/docs/iacref.pdf.
0
 
LVL 8

Expert Comment

by:wguerram
ID: 12034986
'Here is a class you can use to close any parent or child window

'The search is performed using the instr, so it looks for the occurance of the title.

'You could only specify

'_wActions.CloseParentWindow("Acrobat")
this would find and window that has acrobat in his title

'or
'_wActions.CloseChildWindow("Acrobat", "VB .NET")

'You can modify the class in order to make case insensitive.

'Hope this helps.

Private Sub FlatButton1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FlatButton1.Click
        Dim _wActions As New WindowsActions()
        Try
            _wActions.CloseChildWindow("Adobe Acrobat", "VB .NET How to Program 2ed.pdf")
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

        _wActions.CloseParentWindow("Adobe Acrobat")

        _wActions = Nothing
    End Sub


'This is the class, just instanciate it and use its methods
Public Class WindowsActions

    Private Delegate Function EnumChildProc(ByVal hwnd As IntPtr, ByVal lParam As Int32) As Int32

#Region "API declarations"

    Private Const WM_CLOSE As Integer = &H10

    Private Declare Function EnumChildWindows Lib "user32.dll" ( _
        ByVal hWndParent As IntPtr, ByVal lpEnumFunc As EnumChildProc, _
        ByVal lParam As Int32) As Int32

    Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" ( _
        ByVal hwnd As IntPtr, ByVal wMsg As Int32, ByVal wParam As IntPtr, _
        ByRef lParam As Object) As Int32

    Private Declare Function GetWindowTextLength Lib "user32.dll" Alias "GetWindowTextLengthA" _
        (ByVal hwnd As IntPtr) As Int32

    Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" _
        (ByVal hwnd As IntPtr, ByVal lpString As String, ByVal cch As Int32) As Int32

#End Region

    Private _Handle As IntPtr
    Dim _Title As String

    Public Sub CloseParentWindow(ByVal Title As String)
        _Title = Title
        'Search for the window in the desktop
        EnumChildWindows(IntPtr.Zero, AddressOf FindWindow, 0)

        'If not found throw an exception
        If _Handle.Equals(IntPtr.Zero) Then
            Throw New Exception("Parent window """ & Title & """ not found.")
        Else
            'Close window
            SendMessage(_Handle, WM_CLOSE, IntPtr.Zero, 0)
        End If
    End Sub

    Public Sub CloseChildWindow(ByVal ParentTitle As String, ByVal ChildTitle As String)
        _Title = ParentTitle

        'Search for the parent window handle in the desktop
        EnumChildWindows(IntPtr.Zero, AddressOf FindWindow, 0)

        'If parent window not found throw an exception
        If _Handle.Equals(IntPtr.Zero) Then
            Throw New Exception("Parent window """ & ParentTitle & """ not found.")
        End If

        _Title = ChildTitle
        'Search for child window handle
        EnumChildWindows(_Handle, AddressOf FindWindow, 0)

        'If not found throw an exception
        If _Handle.Equals(IntPtr.Zero) Then
            Throw New Exception("Child window """ & ChildTitle & """ not found.")
        Else
            'Close child window
            SendMessage(_Handle, WM_CLOSE, IntPtr.Zero, 0)
        End If
    End Sub

    'Callback function to enum windows
    Private Function FindWindow(ByVal hwnd As IntPtr, ByVal lParam As Int32) As Int32
        Dim sSave As String

        'Get the windowtext length
        sSave = Space(GetWindowTextLength(hwnd) + 1)

        'get the window text
        GetWindowText(hwnd, sSave, Len(sSave))

        'remove the last Chr(0)
        sSave = Microsoft.VisualBasic.Left(sSave, Len(sSave) - 1)

        If Microsoft.VisualBasic.InStr(sSave, _Title) > 0 And sSave <> "" Then
            _Handle = hwnd
            Return 0 'stop enumeration
        Else
            _Handle = IntPtr.Zero
            Return 1 'continue enumeration
        End If
    End Function
End Class
0
 

Author Comment

by:rrouse
ID: 12045463
Hi, and thanks to both wguerram & Javert93.  

I tried implementing the solution provided by wguerram, but it isn't working.  I use "Acrobat" as the name of the parent window (which does not result in an error), but the parent window does not close.  I also believe that the child window cannot be found because the parent window is not being identified.  When using the Process.Start() command, can I tell it the name of the window?  Or, is there a standard Acrobat name used?

I also looked up the link provided by Javert93...very helpeful!  However, I don't know how to use those commands in a .Net solution.  Do you know of some examples I could look up that show how you would code them into an application?  Also, I read somewhere that DDE is not supported in .Net...is this an incorrect statement?  

Again, thanks!
0
 
LVL 8

Expert Comment

by:wguerram
ID: 12045669
If you don't get an error its means the class is finding the window.

Do you have other windows opened while doing this?

I was trying it and i realize that the title for this internet explorer page has Acrobat, so it was finding this window.

Do something:

Place a break point in the line

_Handle = hwnd ' and check the value of sSave variable

Just to see the name of the window is finding

        If Microsoft.VisualBasic.InStr(sSave, _Title) > 0 And sSave <> "" Then
            _Handle = hwnd  '<------- Break point here
            Return 0 'stop enumeration
        Else
            _Handle = IntPtr.Zero
            Return 1 'continue enumeration
        End If
0
Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

 
LVL 8

Expert Comment

by:wguerram
ID: 12045741
Instead of the breakpoint you can also place a messagebox

       If Microsoft.VisualBasic.InStr(sSave, _Title) > 0 And sSave <> "" Then
             MsgBox(sSave)
            _Handle = hwnd  '<------- Break point here
            Return 0 'stop enumeration
        Else
            _Handle = IntPtr.Zero
            Return 1 'continue enumeration
        End If
0
 
LVL 4

Expert Comment

by:Javert93
ID: 12045841
No, DDE is supported through a set of very help Win32 API's. I need to look them up, but I will post a class later on this afternoon that will let you do exactly do everything that you want (including stuff from the last post). I just need some time to translate the API's from C++ and test the code.
0
 
LVL 4

Accepted Solution

by:
Javert93 earned 200 total points
ID: 12047967
Here is the class that I promised you earlier:

'=====> START COPYING HERE <=====

Imports Microsoft.Win32
Imports System.Runtime.InteropServices

Namespace DDE

    Public Class DdeErrorException
        Inherits Exception

        Public Sub New(ByVal procName As String, ByVal code As Integer)
            MyBase.New(String.Format("{0} failed with the following error code : 0x{1:X4}.", procName, code))
        End Sub
    End Class

    Public NotInheritable Class AcrobatDDE
        Implements IDisposable

#Region " Constant Declarations "

        Private Const ACROBAT_DDESERVER As String = "acroview"
        Private Const ACROBAT_DDE_TOPIC As String = "control"

        Private Const APPCMD_CLIENTONLY As Integer = &H10I

        Private Const CF_TEXT As Integer = 1

        Private Const DMLERR_NO_ERROR As Integer = 0
        Private Const DMLERR_NOTPROCESSED = &H4009I

        Private Const HKEY_ACROBAT_READER As String = "SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AcroRd32.exe"

        Private Const XCLASS_FLAGS As Integer = &H4000I
        Private Const XTYP_EXECUTE As Integer = (&H50I Or XCLASS_FLAGS)

#End Region

#Region " Variable Declarations "

        Private InternalTimeout As Integer

        Private DdeInstanceID As Integer
        Private Disposed As Boolean
        Private ConversationHandle As IntPtr
        Private Acrobat As Process

#End Region

#Region " Win32 API Declarations "

        Private Declare Ansi Function DdeClientTransaction Lib "user32.dll" (<[In](), Out()> ByVal pData() As Byte, ByVal cbData As Integer, ByVal hConv As IntPtr, ByVal hszItem As IntPtr, ByVal wFmt As Integer, ByVal wType As Integer, ByVal dwTimeout As Integer, ByRef dwResult As Integer) As IntPtr
        Private Declare Ansi Function DdeConnect Lib "user32.dll" (ByVal idInst As Integer, ByVal hszService As IntPtr, ByVal hszTopic As IntPtr, ByVal pCC As IntPtr) As IntPtr
        Private Declare Ansi Function DdeCreateStringHandle Lib "user32.dll" Alias "DdeCreateStringHandleA" (ByVal idInst As Integer, ByVal psz As String, ByVal iCodePage As Integer) As IntPtr
        Private Declare Ansi Function DdeDisconnect Lib "user32.dll" (ByVal hConv As IntPtr) As Boolean
        Private Declare Ansi Function DdeFreeStringHandle Lib "user32.dll" (ByVal idInst As Integer, ByVal hsz As IntPtr) As Boolean
        Private Declare Ansi Function DdeGetLastError Lib "user32.dll" (ByVal idInst As Integer) As Integer
        Private Declare Ansi Function DdeInitialize Lib "user32.dll" Alias "DdeInitializeA" (ByRef pidInst As Integer, ByVal pfnCallback As DdeCallbackDelegate, ByVal afCmd As Integer, ByVal ulRes As Integer) As Integer
        Private Declare Ansi Function DdeUninitialize Lib "user32.dll" (ByVal idInst As Integer) As Boolean
        Private Declare Ansi Sub Sleep Lib "kernel32.dll" (ByVal dwTime As Integer)

#End Region

#Region " Delegate Declarations "

        Private Delegate Function DdeCallbackDelegate(ByVal uType As Integer, ByVal uFmt As Integer, ByVal hconv As IntPtr, ByVal hsz1 As IntPtr, ByVal hsz2 As IntPtr, ByVal hdata As IntPtr, ByVal dwData1 As IntPtr, ByVal dwData2 As IntPtr) As IntPtr

#End Region

#Region " Class Construction/Destruction "

        Public Sub New()
            InternalTimeout = 3000
            DdeInstanceID = 0

            ' Initialize the DDE subsystem
            Dim result As Integer = DdeInitialize(DdeInstanceID, AddressOf DdeCallback, APPCMD_CLIENTONLY, 0)
            If result <> DMLERR_NO_ERROR Then
                DdeInstanceID = 0
                Throw New DdeErrorException("DdeInitialize", result)
            End If

            ' Initiate a DDE conversation with Acrobat
            Try
                Acrobat = LaunchAcrobat()
                ConversationHandle = InitDdeConversation()
            Catch ex As Exception
                ' Unitialize DDE and re-throw the exception
                ConversationHandle = IntPtr.Zero
                DdeUninitialize(DdeInstanceID)
                Throw ex
            End Try
        End Sub

        Public Sub Dispose() Implements System.IDisposable.Dispose
            Dispose(False)
        End Sub

        Protected Overrides Sub Finalize()
            Dispose(True)
        End Sub

        Protected Sub Dispose(ByVal CalledByFinalize As Boolean)
            If Disposed Then Exit Sub

            If Not ConversationHandle.Equals(IntPtr.Zero) Then
                DdeDisconnect(ConversationHandle)
                ConversationHandle = IntPtr.Zero
            End If

            If DdeInstanceID <> 0 Then
                DdeUninitialize(DdeInstanceID)
            End If

            If Not CalledByFinalize Then GC.SuppressFinalize(Me)
            Disposed = True
        End Sub

#End Region

#Region " Class Properties "

        Public Property Timeout() As Integer
            Get
                Return InternalTimeout
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then Value = -1
                InternalTimeout = Value
            End Set
        End Property

#End Region

#Region " Acrobat-Specific Commands "

        Public Sub AppExit()
            ' Normally we would use the "AppExit" DDE command to close the reader, but this
            ' command causes an exception in version 6.0, so close the main window instead
            If Not Acrobat Is Nothing Then
                Acrobat.CloseMainWindow()
            End If
            Me.Dispose()
        End Sub

        Public Function CloseAllDocs() As Boolean
            Return Execute("CloseAllDocs")
        End Function

        Public Function DocClose(ByVal fileName As String) As Boolean
            Return Execute("DocClose", fileName)
        End Function

        Public Function DocGoTo(ByVal fileName As String, ByVal pageNum As Integer)
            Return Execute("DocGoTo", fileName, pageNum)
        End Function

        Public Function DocGoToNameDest(ByVal fileName As String, ByVal nameDest As String)
            Return Execute("DocGoToNameDest", fileName, nameDest)
        End Function

        Public Function DocOpen(ByVal fileName As String) As Boolean
            Return Execute("DocOpen", fileName)
        End Function

        Public Function FilePrint(ByVal fileName As String)
            Return Execute("FilePrint", fileName)
        End Function

        Public Function FilePrintEx(ByVal fileName As String)
            Return Execute("FilePrintEx", fileName)
        End Function

        Public Function FilePrintSilent(ByVal fileName As String)
            Return Execute("FilePrintSilent", fileName)
        End Function

        Public Function FilePrintSilentEx(ByVal fileName As String)
            Return Execute("FilePrintSilentEx", fileName)
        End Function

        Public Function FilePrintTo(ByVal fileName As String, ByVal printerName As String, ByVal driverName As String, ByVal portName As String)
            Return Execute("FilePrintTo", fileName, printerName, driverName, portName)
        End Function

        Public Function FilePrintToEx(ByVal fileName As String, ByVal printerName As String, ByVal driverName As String, ByVal portName As String)
            Return Execute("FilePrintToEx", fileName, printerName, driverName, portName)
        End Function

#End Region

#Region " Class Methods "

        Public Function Execute(ByVal Command As String, ByVal ParamArray ArgumentList() As Object) As Boolean
            If Disposed Then Throw New ObjectDisposedException("AcrobatDDE")

            ' Build the argument list
            Dim argList As New ArrayList()
            Dim item As Object

            For Each item In ArgumentList
                ' Convert the argument to a string; if it contains
                ' spaces, then it needs to be wrapped in quotes
                Dim arg As String = Convert.ToString(item)
                If arg.IndexOf(" ") > 0 Then arg = String.Format("""{0}""", arg)

                ' Add the argument to the list
                argList.Add(arg)
            Next

            ' Build the command buffer
            Dim cmd As String = String.Format("[{0}({1})]", Command, String.Join(",", argList.ToArray(GetType(String))))
            Dim cmdBuffer As Byte() = System.Text.Encoding.ASCII.GetBytes(cmd)

            ' Execute the DDE command
            Dim result As Integer
            If DdeClientTransaction(cmdBuffer, cmd.Length, ConversationHandle, IntPtr.Zero, _
                CF_TEXT, XTYP_EXECUTE, InternalTimeout, result).Equals(IntPtr.Zero) Then
                Dim errorCode As Integer = DdeGetLastError(DdeInstanceID)
                ' DMLERR_NOTPROCESSED is not an error, it just means the
                ' request failed (usually due to a missing or invalid argument)
                If errorCode <> DMLERR_NOTPROCESSED Then
                    Throw New DdeErrorException("DdeClientTransaction", DdeGetLastError(DdeInstanceID))
                Else
                    ' Return failure
                    Return False
                End If
            End If

            ' Return success
            Return True
        End Function

#End Region

#Region " Internal Callback Functions "

        ' This callback is required by DDE, even though we are not using it
        Private Function DdeCallback(ByVal uType As Integer, ByVal uFmt As Integer, ByVal hconv As IntPtr, ByVal hsz1 As IntPtr, ByVal hsz2 As IntPtr, ByVal hdata As IntPtr, ByVal dwData1 As IntPtr, ByVal dwData2 As IntPtr) As IntPtr
            Return IntPtr.Zero
        End Function

#End Region

#Region " Internal Helper Functions "

        Private Function InitDdeConversation() As IntPtr
            ' Create the string handles used by Acrobat
            Dim hszServer As IntPtr = DdeCreateStringHandle(DdeInstanceID, ACROBAT_DDESERVER, 0)
            Dim hszTopic As IntPtr = DdeCreateStringHandle(DdeInstanceID, ACROBAT_DDE_TOPIC, 0)

            ' Connect to reader's process through DDE
            Dim hConversation As IntPtr
            Dim dwSleep As Integer
            Do
                ' Attempt to connect
                hConversation = DdeConnect(DdeInstanceID, hszServer, hszTopic, IntPtr.Zero)
                If (hConversation.ToInt32() <> 0) Or (dwSleep > 3000) Then Exit Do

                ' Connect failed; give reader more time to launch
                dwSleep += 200
                Sleep(dwSleep)
            Loop

            ' Free the string handles (we don't need them anymore
            DdeFreeStringHandle(DdeInstanceID, hszTopic)
            DdeFreeStringHandle(DdeInstanceID, hszServer)

            ' Check the conversation handle to see if the connect succeeded
            If (hConversation.ToInt32() = 0) Then
                Throw New DdeErrorException("DdeConnect", DdeGetLastError(DdeInstanceID))
            End If

            ' Return the newly created conversation handle
            Return hConversation
        End Function

        Private Function LaunchAcrobat() As Process
            ' Get the command line to the Acrobat Reader
            Dim hRegAcro As RegistryKey = Registry.LocalMachine.OpenSubKey(HKEY_ACROBAT_READER)
            Dim fileName As String = hRegAcro.GetValue(Nothing).ToString()

            ' Launch the reader's process
            Dim p As Process = Process.Start(fileName)
            If p Is Nothing Then Throw New System.IO.FileLoadException("Unable to launch Acrobat Reader.", fileName)

            ' Return the Acrobat reader process
            Return p
        End Function

#End Region

    End Class

End Namespace

'=====> STOP COPYING HERE <=====

I only included the most useful commands that are available to the reader, but if you are using the full version of acrobat and want to include accessors for the other functions, I'm sure you can figure out how to add them by looking at the existing methods. Also, I am going under the assumption that you have read the Adobe documentation and know how to call these function and know what each of them does.
0
 

Expert Comment

by:rmundkowsky
ID: 23317612
This code is quite helpful, but one slight correction. DdeGetLastError when used resets the error code that will be returned next time. So the following change is recommended:

                        'Throw New DdeErrorException("DdeClientTransaction", DdeGetLastError(DdeInstanceID))
                        Throw New DdeErrorException("DdeClientTransaction", errorCode)
0
 

Expert Comment

by:rmundkowsky
ID: 23319629
One more comment, the wFmt parameter to the DdeClientTransaction call should be zero for type XTYP_EXECUTE, code is below.  Also I noted that I was getting a timeout error using InternalTimeout = 3000 for a 4 page PDF, so increasing the timeout seems to help.

If DdeClientTransaction(cmdBuffer, cmd.Length, ConversationHandle, IntPtr.Zero, _
                    0, XTYP_EXECUTE, InternalTimeout, result).Equals(IntPtr.Zero) Then
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Introduction When many people think of the WebBrowser (http://msdn.microsoft.com/en-us/library/2te2y1x6%28v=VS.85%29.aspx) control, they immediately think of a control which allows the viewing and navigation of web pages. While this is true, it's a…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now