Событие Закрытия Документа Word

Bob84

Well-known member
16.05.2012
48
0
#1
Здравствуйте.
Задача поставлена такая - открываем из клиента вордовский документ (по шаблону), при его закрытии спрашиваем пользователя нужно ли сохранить, если да то нужно в клиенте его определенным образом приатачить.
На закрытии документа вызвать какой-то код не проблема (как например описано тут http://word.mvps.org/faqs/macrosvba/documentevents.htm ), но не знаю как в лотусе поймать событие закрытия документа в ворде.
Пробовал писать что-то вроде
Код:
On Event Document_Close From worddoc Call test
получаю сообщение об ошибке.
Сделать на закрытии документа создание какого-то файла, по которому понять, что исходный был успешно сохранен, а в лотусе используя класс таймера ждать пока это не произойдет?
Заранее спасибо.
 

lmike

нет, пердело совершенство
Lotus team
27.08.2008
6 586
272
#2
не используйте в Нотусе технологии от МС.
или не используйте Нотус :D
Для общего понимания:
-то что вызывается из нотуса - это некий интерфейс к ворду
-события кот. генерит ворд не обязаны транслироваться в Нотус
-события лотусскрипта никак не связаны с системой от МС! (а именно их вы пытаетесь задейстовать)
Интерактивная связка с МС инструментами сама по себе работает неустойчиво, а в случае интеграции - отвратительно

Теперь вопрос по существу - зачем позволять пользователю взаимодействовать с вордом, иначе как распечатать?
Существуют различные варианты вставки информации из Нотуса в ворд (или создания файлов др. формата - что более правильно)
 

duchan

Well-known member
20.09.2006
110
3
#3
Для Bob84:
On Event - применим ТОЛЬКО для встроенных классов Notes, так что применить для событий COM\OLE\свой_класс не получится... :(

В качестве варианта попробуйте отслеживать дату\время изменения файла, как только файл меняется (будет сохранени) вы сможете это отследить.
Остается вопрос с отслеживание завершения редактирования (закрытия) документа\приложения - тут без WinAPI уже не обойтись. :( Варианты - это отслеживать по PID процесса (сложно и не всегда корректно) или по заголовку окна (получать список всех окон и перебирая их искать в их заголовках имя Вашего файла). Если открываете через COM\OLE (CreateObject) то можно попробывать отслеживать "жизнь" этого объекта, если документ\приложение закроется то доступа к свойствам уже не будет и вы получите ошибку которую можно перехватить (данный вариант не пробывал сам, но выглядит жизнеспособно, проверять\эксперементировать надо) (учтите что в MS Excel, по крайней мере в "ранних" версиях до 2003 было что если ячейка активна\редактируется, то через COM объект становился не доступен - как сейчас не в курсе)

В качестве примеров куски из "живого кода", так что учтите это (что кроме приведенного кода есть еще что-то) :)
запуск приложения и получения его PID:
Код:
	Private Sub ShellAndWait ( Byval RunProg As String, AutoSave As Variant, Blocked As Variant )
Dim StartInf As STARTUPINFO
StartInf.cb = Len ( StartInf ) 
RetVal = CreateProcessA ( 0&, RunProg , 0& , 0& , 1&, NORMAL_PRIORITY_CLASS , 0& , 0& , StartInf , ProcessInfo )
PID = ProcessInfo.dwProcessId
End Sub
Код:
' Проверка по PID'у
tmpPID&=OpenProcess(PROCESS_QUERY_INFORMATION, 0&, PID) 
If tmpPID& <> 0& Then
'приложение запущено
Else
'нет приложения
End If
Call CloseHandle(tmpPID&)
End Function
Код:
' проверка по WindowCaption
hWnd& = fEnumWindows(apiGetDesktopWindow(), Strleftback(Me.ppFileName, "."))
If hWnd&<>0 Then
'приложение запущено
Else
'нет приложения
End If
Код:
	Private Function fEnumWindows(PhWnd As Long, Find As String) As Long
Dim hWnd As Long, hWndLen As Long
Dim hWndStyle As Long, strCaption As String

hWnd = apiGetWindow(PhWnd, mcGWCHILD)
Do While Not hWnd = 0
strCaption = fGetCaption(hWnd)
If Len(strCaption) > 0 Then
hWndStyle = apiGetWindowLong(hWnd, mcGWLSTYLE)
If hWndStyle And mcWSVISIBLE Then
Capt$ = Ucase(fGetCaption(hWnd))
If Instr(Capt$, Ucase(Find)) <> 0 Then
Code%=0
For i%=0 To hWndCount-1
If hWndOld(i%)=hWnd Then Code%=1
Next
If Code%=0 Then fEnumWindows = hWnd
End If
End If
End If
ChildEnumWindow& = fEnumWindows(hWnd, Find)
If ChildEnumWindow& <>0 Then fEnumWindows = ChildEnumWindow&
hWnd = apiGetWindow(hWnd, mcGWHWNDNEXT)
Loop
End Function
'----------------------------------------------------------------------------------------------
Private Function fGetCaption(Hwnd As Long)
Dim strBuffer As String
Dim intCount As Integer
strBuffer = String$(mconMAXLEN - 1, 0)
intCount = apiGetWindowText(Hwnd, strBuffer, mconMAXLEN)
If intCount > 0 Then
fGetCaption = Left$(strBuffer, intCount)
End If
End Function
'----------------------------------------------------------------------------------------------
Private Function fGetClassName(Hwnd As Long)
Dim strBuffer As String
Dim intCount As Integer
strBuffer = String$(mconMAXLEN - 1, 0)
intCount = apiGetClassName(Hwnd, strBuffer, mconMAXLEN)
If intCount > 0 Then
fGetClassName = Left$(strBuffer, intCount)
End If
End Function
ну и декларации с константами (может что и пропущу у меня там много констант)
Код:
Private Const mcGWCHILD = 5
Private Const mcGWHWNDNEXT = 2
Private Const mcGWLSTYLE = (-16)
Private Const mcWSVISIBLE = &H10000000
Private Const mconMAXLEN = 255


Type STARTUPINFO 
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String 
dwX As Long 
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long 
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long 
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type

Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type


Declare Function RegCloseKey Lib "advapi32.dll" (Byval hKey As Long) As Long
Declare Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" (Byval hKey As Long, Byval lpSubKey As String, phkResult As Long) As Long
Declare Function RegOpenKeyEx Lib "advapi32" Alias "RegOpenKeyExA" (Byval hKey As Long, Byval lpSubKey As String, Byval ulOptions As Long, Byval samDesired As Long, phkResult As Long) As Long
Declare Function RegQueryValueEx Lib "advapi32" Alias "RegQueryValueExA" (Byval hKey As Long, Byval lpValueName As String, Byval lpReserved As Long, lpType As Long, Byval lpData As String, lpcbData As Long) As Long

Declare Function WaitForSingleObject Lib "kernel32" (Byval hHandle As Long, Byval dwMilliseconds As Long) As Long
Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (Byval hwnd As Long, Byval lpOperation As String, Byval lpFile As String, Byval lpParameters As String, Byval lpDirectory As String, Byval nShowCmd As Long) As Long
Declare Function CreateProcessA Lib "kernel32" (Byval lpApplicationName As Long, Byval lpCommandLine As String, Byval lpProcessAttributes As Long, Byval lpThreadAttributes As Long, Byval bInheritHandles As Long, Byval dwCreationFlags As Long, _
Byval lpEnvironment As Long, Byval lpCurrentDirectory As Long, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Declare Function CloseHandle Lib "kernel32" (Byval hObject As Long) As Long 
Declare Function OpenProcess Lib "kernel32" (Byval dwDesiredAccess As Long, Byval bInheritHandle As Long,Byval dwProcessId As Long) As Long


Declare Function apiGetClassName Lib "user32" Alias "GetClassNameA" (Byval Hwnd As Long, Byval lpClassname As String, Byval nMaxCount As Long) As Long
Declare Function apiGetDesktopWindow Lib "user32" Alias "GetDesktopWindow" () As Long
Declare Function apiGetWindow Lib "user32" Alias "GetWindow" (Byval Hwnd As Long, Byval wCmd As Long) As Long
Declare Function apiGetWindowLong Lib "user32" Alias "GetWindowLongA" (Byval Hwnd As Long, Byval nIndex As Long) As Long
Declare Function apiGetWindowText Lib "user32" Alias "GetWindowTextA" (Byval Hwnd As Long, Byval lpString As String, Byval aint As Long) As Long
Declare Function ShowWindow Lib "user32" (Byval hwnd As Long, Byval nCmdShow As Long) As Long
Declare Function BringWindowToTop Lib "user32" (Byval hwnd As Long) As Long

P.S. для lmike: использовать или не использовать, глючит или не глючит - это холивар. Хотя, да согласен что применение сторонних приложений\модулей\технологий накладывает существенные ограничения как на реализацию, так и на мультиплотформенность. Но всегда необходимо исходить из релей задачи, анализируя потери, затраты и получаемый профит.
 

garrick

Lotus team
26.10.2009
901
61
#4
Если вы кликаете на приаттаченный файл MS Office, Lotus Notes вас спрашивает "Open", "Edit", etc. Если вы выбираете "Edit", то при закрытии файла Lotus Notes сам спрашивает надо ли сохранить изменённый аттачмент. Версия 8.5 с различными вариациями.
 

lmike

нет, пердело совершенство
Lotus team
27.08.2008
6 586
272
#5
DuChan искать инстанс можно и по имени файла, например так http://codeby.net/forum/threads/51696.html
о целях/задачах - да, бывают разные, но сразу кидаться в "пучину" тоже не стоит :(
у меня нет уверенности, что кусок из задачи, кот. описан выше, необходимо решать именно так, как описывает автор...
вернее есть уверенность - что так не нужно :)
если в результате нужно получить документ по шаблону - совершенно не обязательно предоставлять пользователю возможность корячить текст в ворде, ибо прозвучало слово шаблон
как уже не раз описывалось (и не только здесь) - можно просто вставить куски текста из нотуса в шаблон ворда (если на выходе нужен именно ворд), этим - максимально отстранить пользователя от использования 2-ух инструментов одновременно (что само по себе "чревато")
 

Bob84

Well-known member
16.05.2012
48
0
#6
Спасибо за ответы.
Для lmike - к несчатью именно так задача и поставлена, ждать пока пользователь не завершит работу с документом.
 

Bob84

Well-known member
16.05.2012
48
0
#7
DuChan
т.е. проверку по PID или WindowCaption нужно загонять в цикл и ждать пока не закроется. Таким образом клиент будет висеть все это время. Может тогда лучше использовать таймер?
 

lmike

нет, пердело совершенство
Lotus team
27.08.2008
6 586
272
#8
Bob84 повторюсь - задача не корректна
нельзя комфортно работать в 2-ух связанных приложениях, причем одно из них (нотус) из основного становится подчиненным!
Зачем так делать?
это просто хотелки начальства?
 

savl

Lotus team
28.10.2011
2 136
105
#10
Bob84
Так в чем проблема тогда? garrick, все верно сказал.
Если просто на поле расположить RT-поле, то в нем можно хранить файлы.
При открытии будет спрашивать в каком режиме открывать, если файл будет изменен и пользователь скажет сохранить на диалог лотуса, он будет перезаписан в документ сам.

Этот подход не подойдет - если у вас хранилище файлов отдельное.
 

Bob84

Well-known member
16.05.2012
48
0
#11
Хранилище отдельное, поэтому я больше склоняюсь к варианту DuChan.
 

savl

Lotus team
28.10.2011
2 136
105
#12
Bob84
Печаль...
Так как это отдельное хранилище, скорее всего и было сделано для того, чтобы отслеживать изменения файлов.
Наверняка там есть фамилия автора, дата добавления и краткое описание.
Следовательно надо будет это менять после сохранения. Еще наверняка не на каждом статусе при согласовании надо давать редактировать документ, а то вся целостность к чертям... Я конечно утрирую, но всякое бывает.
 

lmike

нет, пердело совершенство
Lotus team
27.08.2008
6 586
272
#13
Bob84 а огласить ТЗ можно?
чета там очень мутно - непонятно зачем Нотус в это связке?
 

erdi

Well-known member
20.08.2008
265
17
#14
в копилку бредовых идей....как-то возникала, но так в качестве одного из вариантов и осталась
при открытии документа - модифицировать его, т.е. добавлять ему макрос на закрытие
в этом макросе, опять же, все через тот же ком дергать лотус-документ и модифицировать его. т.к. макрос будешь добавлять через код, то и соответственно в код уже программно заложишь uid документа, который необходимо модифицировать. в случаи если лотус открыт - пароль не потребуется вводить
но это грабли, грабли и еще раз грабли - Программирование как секс. Одна ошибка - и ты должен поддерживать её всю оставшуюся жизнь
 

lmike

нет, пердело совершенство
Lotus team
27.08.2008
6 586
272
#15
savl следуя нормальной логике...
-откуда заполняется шаблон - если из Нотуса, то зачем интерактив
-если конечный док кладется в Нотус - то зачем еще и хранилище

Добавлено: я до сих пор не пойму - что мешает вести документооборот в Нотусе, а результат, коли это так надо, рендерить в ворд, по шаблону?
 

savl

Lotus team
28.10.2011
2 136
105
#16
Программирование как секс. Одна ошибка - и ты должен поддерживать её всю оставшуюся жизнь
Это не баг - это фича :)

lmike
Я только сейчас понял что речь про шаблоны.
Это кстати, действительно глупо... Можно тянуть все в word из формы, это факт и потом сразу крепить.
Это если он кладется в лотус, хранилище лотусовое именно.
Но видимо "эффективные менеджеры" решили, что это долго и написать "костыль", чтобы док крепился сам - проще и быстрее.
-если конечный док кладется в Нотус - то зачем еще и хранилище
А вот дает подозрение, что хранилище не лотусовое.
В лотусе док содержит meta-ссылку, отображается этот док во встроенном представлении.
При открытии идет обработка ссылки через технологию хранилища и открывается док, который сохраняется в temp.
Я такое видел, на прошлом месте, там исторически сложилось + большой объем документов, свыше 1 млн действующих договор с дополнениями, актами, протоколами и так далее.
 

Bob84

Well-known member
16.05.2012
48
0
#17
Хранилище лотусовое, постановка из ТЗ
При нажатии на кнопку для формирования соответствующего документа, открывается файл формата MS Word, заполненный автоматически данными из текущего документа. Пользователь может внести изменения в файл.
Затем при закрытии сформированного файла выдается сообщение "Сохранить файл в системе?" с вариантами ответа "Да"/"Нет". При выборе ответа "Да", сформированный документ сохраняется в БД Хранилище эл. образов и отображается в списке вложений текущего документа. При выборе варианта "Нет" файл не сохраняется в системе.
 

Bob84

Well-known member
16.05.2012
48
0
#18
в копилку бредовых идей....как-то возникала, но так в качестве одного из вариантов и осталась
при открытии документа - модифицировать его, т.е. добавлять ему макрос на закрытие
в этом макросе, опять же, все через тот же ком дергать лотус-документ и модифицировать его. т.к. макрос будешь добавлять через код, то и соответственно в код уже программно заложишь uid документа, который необходимо модифицировать. в случаи если лотус открыт - пароль не потребуется вводить
Через COM разве не на бэкенде только можно работать с лотусом? А в моем случае нужно с фронтэндом работать.
 

erdi

Well-known member
20.08.2008
265
17
#19
в макросе, допустим создание документа по форме и заполнением поля uid документа, который должен быть открыт(из которого открыли word документ). В форме на postopen по этому полу получение uidoc открытого документа
Set notesUIDocument = notesUIWorkspace.EditDocument( [editMode] , [notesDocument] , [notesDocumentReadOnly] , [documentAnchor$] , [returnNotesUIDocument] , [newInstance] ) - newInstance=true тогда если док открыт - ты получишь этот объект и тогда уже можешь делать с ним что хочешь

посмотри базу Microsoft Office Library (doclbm7.ntf) может ее тебе будет достаточно или хотя бы принцип работы позаимствуешь
 

erdi

Well-known member
20.08.2008
265
17
#20
а вообще задача какая-то карявая....
данные хранятся в лотус - формируется word из шаблона - правится инфа в word и надо уже не данные измененные вложить, а некий файл...а как же данные, которые в лотус?
не проще ли после сформированного word файла(желательно сохранить не в формате doc, а в rtf, т.к. rtf всеже текст, а не бинарный формат) и потом импортировать этот документ в лотус
Код:
	Dim ws As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set uidoc = ws.CurrentDocument
Call uidoc.GotoField("Body")
Call uidoc.Import("Microsoft RTF", "c:\123.rtf")
тем-самым получишь правильно заполненный формат документа, который можно редактировать, но при этом все это уже лежит он в лотус, а на close документа можешь уже повторно выгружать данные в файл и атачить куда угодно в хранилище
в такой связке даже ком не потребуется....все можно сделать на сервере