10 件 見つかりました。
[2007-01-31-01]に「javascriptによるPDFリンク制御は上書きのみ」などと書いたが、やってみたらできた。また、[2007-01-08-01]にCOMからのリンク作成は無理っぽいなどと書いたが、これもjavascriptを経由しできた。顛末をメモする。WinXP SP3 Acrobat 9.1.3 pro
まずは結論。以下のスクリプトが動いた。
python(PyWin32):
import win32com.client
import winerror
win32com.client.dynamic.ERRORS_BAD_CONTEXT.append(winerror.E_NOTIMPL) # omajinai
doc = win32com.client.Dispatch("AcroExch.App").GetActiveDoc().GetPDDoc()
jso = doc.GetJSObject() # <= this requires omajinai
link = jso.addLink(0, (70,70,225,50))
link.borderWidth = 3
link.borderColor = (u'RGB', 0, 0, 255)
link.setAction("this.pageNum = 2")
var linkRect = [70,70,225,50];
var link1= this.addLink(0, linkRect);
link1.borderWidth = 3;
link1.borderColor = color.blue;
link1.setAction("this.pageNum = 2")
pywin32のwin32comでCOMクライアントを作成する際必要となる定数やメソッドをどう取得するか?
makepy.pyによってStaticDispatch用モジュールを作成すれば良い。
タイプライブラリ名が判っている場合は以下でOK。C:\Python25\Lib\site-packages\win32com\gen_py 中に作成されたモジュール(ファイル)中に記載されている。
C:\Python25\Lib\site-packages\win32com\client>python makepy.py "Igor Pro 6.0 Type Library"DLLや実行ファイルを以下のように直接指定してもOK。
C:\Python25\Lib\site-packages\win32com\client>python makepy.py typelib, "C:\Program Files\hoge\fuga.exe"
[2008-08-16]の続き。ちょっと実際に試してわかったことをメモする。
結論:ResEditを使う際、win32rcparser.pyを変更した方が良い。変更箇所はこのメモ後半に示す。
ResEditで配置する場合、いくつか「地雷」なコントロールがある。
(1) チェックボックス、ラジオボタン
チェックボックスやラジオボタンを配置したリソーススクリプトを使うと、pythonスクリプト実行時ダイアログ自体も表示されなかった。
ResEditでチェックボックス(ラジオボタンも同様)を配置すると以下のようなリソーススクリプトとなる。win32rcparser.pyはこれをちゃんと解釈してくれないようだ。
AUTOCHECKBOX "Check1", IDC_CHECKBOX1, 10, 43, 40, 10C:\Python25\Lib\site-packages\win32\test\win32rcparser\test.rc を見ると以下の記述は正しく解釈されているようだった。
CONTROL "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,52,68,12両者はリソーススクリプト的には等価だが、前者の記述はwin32rcparser.pyは想定してなかった、といったところか。
CONTROL "", IDC_SPIN1, UPDOWN_CLASS, UDS_ARROWKEYS, 10, 119, 9, 14
CONTROL "Spin1",IDC_SPIN1,"msctls_updown32",UDS_ARROWKEYS,7,71,14,22というわけで、ResEditのウィンドウクラスの表現(UPDOWN_CLASS)がwin32rcparserに解釈できなかった、という感じ。ピクチャコントロールも同様に躓いた模様。
--- C:/Python25/Lib/site-packages/win32/lib/win32rcparser_orig.py Thu Jul 29 00:25:22 2004
+++ C:/temp/e/f/win32rcparser.py Sun Aug 17 14:25:20 2008
@@ -199,10 +199,12 @@
def parseH(self, file):
lex = shlex.shlex(file)
lex.commenters = "//"
token = " "
+ lex.whitespace += '()'
+ lex.wordchars += '-'
while token is not None:
token = lex.get_token()
if token == "" or token is None:
token = None
else:
@@ -456,10 +458,17 @@
self.getCommaToken()
if control.controlType == "CONTROL":
self.getToken()
control.subType = self.token[1:-1]
+
+ # ad hoc handling for ResEdit's common-controls by H.Itoh
+ # ex. 'UPDOWN_CLASS' => 'UPDOWN_CLASSA' => "msctls_updown32" (see commctrl l.731) etc.
+ try: control.subType = getattr(commctrl, self.token+'A')
+ except AttributeError: pass
+ if self.token=='WC_STATIC': control.subType = 'Static'
+
thisDefaultStyle = defaultControlStyle | \
_addDefaults.get(control.subType, 0)
# Styles
self.getCommaToken()
self.getToken()
@@ -483,10 +492,21 @@
self.getToken()
control.style, control.styles = self.styles([], thisDefaultStyle)
if self.token==",":
self.getToken()
control.styleEx, control.stylesEx = self.styles([], defaultControlStyleEx)
+
+ # ad hoc handling for ResEdit by H.Itoh
+ f = lambda s: control.controlType.endswith(s)
+ if f('CHECKBOX') | f('RADIOBUTTON'):
+ for s in ['BS_'+control.controlType, 'WS_TABSTOP']:
+ control.styles.append(s)
+ control.style |= getattr(win32con, s)
+ control.controlType = 'CONTROL'
+ control.subType = 'Button'
+ control.style |= defaultControlStyle
+
#print control.toString()
dlg.controls.append(control)
def ParseStreams(rc_file, h_file):
rcp = RCParser()
from pywin.mfc import dialog
import win32rcparser
class Mydialog(dialog.Dialog):
def __init__(self, ddef):
dialog.Dialog.__init__(self, ddef)
def OnInitDialog(self):
return dialog.Dialog.OnInitDialog(self)
if __name__=='__main__':
rc = win32rcparser.Parse('my.rc')
ddef = rc.dialogs['IDD_DIALOG1']
w = Mydialog(ddef)
w.DoModal()
[2008-06-09-2]で、pythonによるwindowsプログラミングにおいてリソース(スクリプト/エディタ)が使えそうである旨メモした。
その時はリソーススクリプトをパースする在野のpythonスクリプトを見つけ出したと思っていたが、実は手元のpywin32(-211)に同様のものが含まれていた。
本体は C:\Python25\Lib\site-packages\win32\lib\win32rcparser.py にあり、C:\Python25\Lib\site-packages\win32\Demos\win32rcparser_demo.py でその動作っぷりが確かめられる。これで、個人的には、pythonによるwindowsプログラミングの敷居がぐっと下がったのではと感じる。
ただこれをResEditで利用する場合、そのままでは動かない。以下を参照。
- ANSI版のResEditを使うべき。Unicode版はUTF16のリソーススクリプトを吐くが、win32rcparser.py はこれをうまく読んでくれなかった。本来はwin32rcparser.pyに修正を施すべきだが、これは面倒。作成後にリソーススクリプトをANSIに変換しても良いけれど、そんなことするくらいなら最初からANSI版を使うのが良い。
- ResEditの吐くヘッダ中の
#define IDC_STATIC (-1)が、そのままでは通らない。これは、カッコと負号がちゃんとパースされていないため。この行を編集(消すなり、カッコ無しの正数にしたり)するのがひとつの対策。即ちアプリケーション本体の実行時に動的にこの行をフィルタしてwin32rcparserに渡す、など。もうひとつの対策は、win32rcparser.pyに修正を施すこと。場当たり的だが以下のような簡単に修正できるので、こちらの方が良さそう。
@@ -199,10 +199,12 @@
def parseH(self, file):
lex = shlex.shlex(file)
lex.commenters = "//"
token = " "
+ lex.whitespace += '()'
+ lex.wordchars += '-'
while token is not None:
token = lex.get_token()
if token == "" or token is None:
token = None
else:
下記は、windowsのAPIのSendInputをpythonから呼ぶ例。
Adobe Readerに右矢印入力を10発送りこむ(開いてるPDFを10ページ進める)。エラー処理は省略。
この手のことはAutoHotKeyが専門だけど、pythonでできるにこしたことはない。
AppendKeyInput で win32con.KEYEVENTF_KEYUP を入れないと、同じキーの連続入力が出来ない。
http://mail.python.org/pipermail/python-win32/2005-April/003131.html を参照。
from ctypes import *
import win32ui, win32con
PUL = POINTER(c_ulong)
class KeyBdInput(Structure):
_fields_ = [("wVk", c_ushort),
("wScan", c_ushort),
("dwFlags", c_ulong),
("time", c_ulong),
("dwExtraInfo", PUL)]
class HardwareInput(Structure):
_fields_ = [("uMsg", c_ulong),
("wParamL", c_short),
("wParamH", c_ushort)]
class MouseInput(Structure):
_fields_ = [("dx", c_long),
("dy", c_long),
("mouseData", c_ulong),
("dwFlags", c_ulong),
("time",c_ulong),
("dwExtraInfo", PUL)]
class Input_I(Union):
_fields_ = [("ki", KeyBdInput),
("mi", MouseInput),
("hi", HardwareInput)]
class Input(Structure):
_fields_ = [("type", c_ulong),
("ii", Input_I)]
def AppendKeyInput(vk):
ret = []
extra = c_ulong(0)
ii_ = Input_I()
scan = windll.user32.MapVirtualKeyA(vk, 0)
ii_.ki = KeyBdInput(vk, scan, 0, 0, pointer(extra) )
ret.append( ( win32con.INPUT_KEYBOARD, ii_ ) )
ii2_ = Input_I()
scan = windll.user32.MapVirtualKeyA(vk, 0)
ii2_.ki = KeyBdInput(vk, scan, win32con.KEYEVENTF_KEYUP, 0, pointer(extra) )
ret.append( ( win32con.INPUT_KEYBOARD, ii2_ ) )
return ret
def ExecSendInput(inputs):
num = len(inputs)
FInputs = Input * num
x = FInputs(*inputs)
windll.user32.SendInput(num, pointer(x), sizeof(x[0]))
if __name__ == '__main__':
inputs = []
for i in range(10):
inputs += AppendKeyInput(win32con.VK_RIGHT)
acrobat = win32ui.FindWindowEx(None, None, 'AdobeAcrobat', None)
acrobat.SetForegroundWindow()
ExecSendInput(inputs)
なんちゃってwin32api(あるいはMFC)環境であるpywin32でビットマップを扱う際、意外と手間取ったのでメモ。
やりたかったことは、キャプチャしたビデオ信号のリアルタイム表示。つまり頻繁にやってくるビットマップのデータ列を頻繁に描画したいという状況。Windowsでのビットマップの描画方法は、私の理解では、多分、(有効な)ビットマップハンドルの取得方法に帰着する。なのでデータ列からビットマップハンドルを如何に作るかがここでのお題となる。今のところ、ctypesでCreateDIBSectionを呼出すのが良いと思っており、実際問題なく動作している。
win32gui.LoadImage や PyCBitmap.LoadBitmapFile は単純で簡単だが、リアルタイム表示においていちいちファイルを介するのは速度的に論外。StringIOでメモリ上にファイルオブジェクトを作成すれば速度の問題は解決するかと思い PyCBitmap.LoadBitmapFile でやってみたが、しばらくすると「このコマンドを実行する記憶域がありません」と例外が飛んでくるので、この方法も無理。
そもそもこんな状況ではwin32apiのCreateDIBSectionの使用が定石のひとつと思うが、残念なことにpywin32にこれは定義されていない。(Windows のデバイスコンテキストをビットマップファイルに変換する。Python でも参照)なのでctypesを介してこれを呼出すことにした。
とはいえ私はctypesの動作を深く理解していない(なので下記にいちいちメモをする)。そこでpygletのソースから該当部分を拝借する方法をとった。
CreateDIBSectionにはBITMAPINFOHEADERを渡す必要がある。これを定義するには如何のようにする(window/win32/types.py より)。
from ctypes import *
from ctypes.wintypes import *
class BITMAPINFOHEADER(Structure):
_fields_ = [
('biSize', DWORD),
('biWidth', LONG),
('biHeight', LONG),
('biPlanes', WORD),
('biBitCount', WORD),
('biCompression', DWORD),
('biSizeImage', DWORD),
('biXPelsPerMeter', LONG),
('biYPelsPerMeter', LONG),
('biClrUsed', DWORD),
('biClrImportant', DWORD),
]
def GetBitmapInfoHeader(w, h):
header = BITMAPINFOHEADER()
header.biSize = sizeof(header)
header.biWidth = w
header.biHeight = h
header.biPlanes = 1
header.biBitCount = 24
header.biCompression = 0
header.biSizeImage = w*h*3
header.biXPelsPerMeter = 0
header.biYPelsPerMeter = 0
header.biClrUsed = 0
header.biClrImportant = 0
return header
dataptr = c_void_p()
bitmap = windll.gdi32.CreateDIBSection(self.GetDC().GetHandleOutput(),
byref(GetBitmapInfoHeader(w, h)),
win32con.DIB_RGB_COLORS, byref(dataptr), None, 0)
memmove(dataptr, buf, len(buf))
GDIオブジェクト、デバイスコンテキスト、メモリデバイスコンテキストといった単語は作業中はなんとなく解ったつもりでいるが、少し間をおくと木端微塵に忘れる。pywin32で描画のコードを書こうとしたが、やっぱりすっかり忘れていて参った。動いたコードをメモしておく。
from pywin.mfc import dialog
import win32con, win32ui
class Mydialog(dialog.Dialog):
dlgstyle = (win32con.WS_DLGFRAME | win32con.DS_MODALFRAME | win32con.WS_POPUP |
win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU )
tmpl = [ ["my dialog", (0, 0, 200, 100), dlgstyle], ]
def __init__(self): dialog.Dialog.__init__(self, self.tmpl)
def OnPaint(self):
dc = self.GetDC()
# copy itself
dc.BitBlt((0,0), (20,20), self.GetWindowDC(), (0,0), win32con.SRCCOPY)
# line
dc.LineTo((30,30))
# line with a customized pen
cpen = win32ui.CreatePen(win32con.PS_SOLID, 5, 10000000)
dc.SelectObject(cpen)
dc.LineTo((50,50))
# drawing on memory device context
dcmem = dc.CreateCompatibleDC()
cbmp = win32ui.CreateBitmap()
cbmp.CreateCompatibleBitmap(dc, 100, 100)
dcmem.SelectObject(cbmp)
dcmem.SelectObject(cpen)
dcmem.LineTo((30,30))
dc.BitBlt((50,0), (50,50), dcmem, (0,0), win32con.SRCCOPY)
self._obj_.OnPaint()
d = Mydialog()
d.DoModal()
[2008-06-07]でPyWinのMFC(GUI)プログラミングが有望っぽい事をメモした。
さてGUIプログラミングではコントロールの配置がキモである。ソース内にベタ書きするのは、ファイルが1つで済むので好きだけど、ある程度の規模に膨れるとそうも言ってられなくなる。Visual Studioによるwin32/MFC開発ではリソースエディタでもってコントロールの配置を行い、それをリソーススクリプトに保存するが、これが中々具合が良い。これをPyWin32にも活用できないものだろうか?
ResEditなるものがあるようだ。ちょっと導入してみたけど中々良い。リソースの作成はこれで大丈夫そう。(Visual Studioが使えない時という意味で)
では如何にそれをpythonに喰わせるか?という話になる。[Spambayes-checkins] spambayes/Outlook2000/dialogs/resources rcparser.py, NONE, 1.1.2.1 にそれっぽいコードがある。今度試してみよう。
pywin32のmfcラッパを試している。pythonで結構簡単にWindowsのダイアログが作れて、良さそう。
とりあえず手元で試して動いたコードをメモ。
とにかくダイアログを出す、という例が以下。拡張子をpywにして保存しダブルクリックすればOK。
from pywin.mfc import dialog
import win32con
class Mydialog(dialog.Dialog):
dlgstyle = (win32con.WS_DLGFRAME | win32con.DS_MODALFRAME | win32con.WS_POPUP |
win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU )
btnstyle = (win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP |
win32con.WS_CHILD | win32con.WS_VISIBLE)
dlgButton = 0x0080
tmpl = [
["my dialog", (0, 0, 200, 100), dlgstyle], # dialog
[dlgButton, "my OK", win32con.IDOK, (5, 55, 190, 40), btnstyle], # button
]
def __init__(self):
dialog.Dialog.__init__(self, self.tmpl)
d = Mydialog()
d.DoModal()
from pywin.mfc import dialog
import win32con
class Mydialog(dialog.Dialog):
dlgstyle = (win32con.WS_MINIMIZEBOX | win32con.WS_DLGFRAME |
win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE |
win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT )
btnstyle = (win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP |
win32con.WS_CHILD | win32con.WS_VISIBLE)
IDC_MYBUTTON = 1025
dlgButton = 0x0080
tmpl = [
["my dialog", (0, 0, 200, 100), dlgstyle], # dialog
[dlgButton, "my OK", win32con.IDOK, (5, 5, 190, 40), btnstyle], # button
[dlgButton, "my button", IDC_MYBUTTON, (5, 55, 190, 40), btnstyle], # button
]
def __init__(self):
dialog.Dialog.__init__(self, self.tmpl)
def OnInitDialog(self):
rc = dialog.Dialog.OnInitDialog(self)
self.HookCommand(self.OnMyButton, self.IDC_MYBUTTON)
self.btn = self.GetDlgItem(self.IDC_MYBUTTON)
return rc
def OnMyButton(self, id, cmd):
if cmd==win32con.BN_CLICKED:
ret = self.MessageBox('hello, my button', 'my caption', win32con.MB_YESNO)
if ret==win32con.IDYES: cap = 'yes'
else: cap = 'no'
self.btn.SetWindowText(cap)
def OnOK(self):
rc = dialog.Dialog.OnOK(self)
self.MessageBox('OnOK!', 'farewell')
return rc
def OnCancel(self):
rc = dialog.Dialog.OnCancel(self)
self.MessageBox('OnCancel!', 'farewell')
return rc
d = Mydialog()
d.DoModal()
self.btn = win32ui.CreateButton()
self.btn.CreateWindow('my button', self.btnstyle, (10, 75, 340, 140), self, self.IDC_MYBUTTON)
from pywin.mfc import dialog
import win32con
class Mydialog(dialog.Dialog):
dlgstyle = (win32con.WS_MINIMIZEBOX | win32con.WS_DLGFRAME |
win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE |
win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT )
btnstyle = (win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP |
win32con.WS_CHILD | win32con.WS_VISIBLE)
staticstyle = (win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.SS_LEFT)
IDC_MYBUTTON = 1025
IDC_MYSTATIC = 1026
dlgButton = 0x0080
dlgStatic = 0x0082
tmpl = [
["my dialog", (0, 0, 200, 130), dlgstyle, None, (14,'Arial')], # dialog
[dlgButton, "my OK", win32con.IDOK, (5, 5, 190, 40), btnstyle], # button
[dlgButton, "my button", IDC_MYBUTTON, (5, 55, 190, 40), btnstyle], # button
[dlgStatic, "my static", IDC_MYSTATIC, (5,105, 190, 20), staticstyle], # static
]
timerid = 1
def __init__(self):
dialog.Dialog.__init__(self, self.tmpl)
def OnInitDialog(self):
rc = dialog.Dialog.OnInitDialog(self)
self.HookCommand(self.OnMyButton, self.IDC_MYBUTTON)
self.static = self.GetDlgItem(self.IDC_MYSTATIC)
self.timerflag = False
self.timercount = 0
return rc
def OnMyButton(self, id, cmd):
if cmd==win32con.BN_CLICKED:
if self.timerflag: self.KillTimer(self.timerid)
else: self.SetTimer(self.timerid, 500) # 0.5s
self.timerflag = not self.timerflag
def OnTimer(self, nIDEvent):
self.timercount += 1
self.static.SetWindowText('timer! %d' % self.timercount)
def OnOK(self):
rc = dialog.Dialog.OnOK(self)
self.MessageBox('OnOK!', 'farewell')
return rc
def OnCancel(self):
rc = dialog.Dialog.OnCancel(self)
self.MessageBox('OnCancel!', 'farewell')
return rc
d = Mydialog()
d.DoModal()
pythonでCOMを使うためPython for Windows extensions (see also: Mark Hammond's Free Stuff)を導入した。他の機能にも触れてみようとしている。mfcまわりもラップされているのでGUIアプリケーションも書けるようだ。windowsに特化するなら、MFCへの慣れを勘案すれば、wxPythonよりこっちを使う方がラクかな?
とりあえず簡易な入力ダイアログが非常に簡単に作成できるようなのでメモ。後々重宝しそうな気がする。
import pywin.mfc.dialog
print pywin.mfc.dialog.GetSimpleInput("my textbox title", 'my default value', 'my window title')
ChangeLog INDEX