Hi there.
I would like to be able to formal text in an edit control - e.g. make some lines bold, include a hyperlink that can be clicked like a button... the like.
Is there anything to support that in AHK V2? Or workarounds that I could use?
Thanks Spitzi
rich text edit control in AHK V2?
Re: rich text edit control in AHK V2?
@Spitzi - check out Clipster posted 2 days ago by The Automator:
Sep 19, 2023 Sadly sending keystrokes is becoming unreliable. We wrote a function that pastes the text but much more! It also will: Navigate to a folder, Paste HTML or Paste Images. Check it out in this video, you can get the script here https://the-Automator.com/Clipster
Sep 19, 2023 Sadly sending keystrokes is becoming unreliable. We wrote a function that pastes the text but much more! It also will: Navigate to a folder, Paste HTML or Paste Images. Check it out in this video, you can get the script here https://the-Automator.com/Clipster
-
- Posts: 4358
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: rich text edit control in AHK V2?
RichEdit:
Code: Select all
#Requires AutoHotkey v2.0
#DllLoad 'Msftedit.dll'
styles := (ES_SAVESEL := 0x00008000) | (ES_NOHIDESEL := 0x00000100) | (ES_MULTILINE := 0x00000004)
EN_LINK := 0x70B, ENM_LINK := 0x04000000, tomNullCaret := 2, WM_LBUTTONDOWN := 0x0201
EM_SETREADONLY := 0x00CF, EM_SETEVENTMASK := 0x0445, EM_SETRECT := 0x00B3
imagePath := 'pic.png'
if !FileExist(imagePath) {
Download 'https://i.imgur.com/26lSSUi.png', imagePath
}
wnd := Gui(, 'RichEdit Test')
wnd.MarginX := wnd.MarginY := 15
re := wnd.AddCustom('ClassRICHEDIT50W x15 y15 w395 h250 ' . styles)
SendMessage EM_SETEVENTMASK,, ENM_LINK, re
NumPut('Int', 14, 'Int', 18, 'Int', 373, 'Int', 225, RECT := Buffer(16))
SendMessage EM_SETRECT,, RECT, re
TextDocument := GetDispatch(re.hwnd)
TextDocument.CaretType := tomNullCaret
rng := CreateRange(0, 0, 'Calibri', 28, 0x38D3E2)
rng.Text := 'This is a RichEdit control'
rng := CreateRange(rng.End, rng.End,, 5)
rng.Text := '`n`n'
rng := CreateRange(rng.End, rng.End, 'Consolas', 18)
rng.Text := 'AutoHotkey'
rng.Url := '"https://www.autohotkey.com/boards/viewtopic.php?p=540158#p540158"'
rng := CreateRange(rng.End, rng.End, 'Consolas', 18)
rng.Text := ' 🠔 this is a link'
rng := CreateRange(rng.End, rng.End,, 12)
sp := ' '
Loop 9 * A_ScreenDPI // 100 {
sp .= A_Space
}
rng.Text := '`n`n`n' . sp
InsertPicture(imagePath, CreateRange(rng.End, rng.End), 2000, 2000) ; HIMETRIC := 0.01 mm
SendMessage EM_SETREADONLY, true,, re
wnd.Show()
re.OnNotify(EN_LINK, (e, lParam) => (
NumGet(lParam, A_PtrSize * 3, 'UInt') = WM_LBUTTONDOWN && (
start := NumGet(lParam, A_PtrSize * 5 + 4, 'UInt'),
end := NumGet(lParam, A_PtrSize * 5 + 8, 'UInt'),
Run(TextDocument.Range(start, end).Text)
)
))
CreateRange(start, end, fontName := 'Verdana', fontSize := 12, fontColor?) {
rng := TextDocument.Range(start, end)
rng.Font.Name := fontName
rng.Font.Size := fontSize
IsSet(fontColor) && rng.Font.ForeColor := fontColor
return rng
}
InsertPicture(imagePath, rng, w, h) {
static VT_UNKNOWN := 13, TA_BASELINE := 0x18
buf := FileRead(imagePath, 'RAW')
pIStream := DllCall('Shlwapi\SHCreateMemStream', 'Ptr', buf, 'UInt', buf.size, 'Ptr')
rng.InsertImage(w, h, 1, TA_BASELINE, '', ComValue(VT_UNKNOWN, pIStream))
}
GetDispatch(hEdit) {
static EM_GETOLEINTERFACE := 0x43C, VT_DISPATCH := 9, VT_UNKNOWN := 13, F_OWNVALUE := 1
, IID_ITextDocument2 := '{01C25500-4268-11D1-883A-3C8B00C10000}'
DllCall('SendMessage', 'Ptr', hEdit, 'UInt', EM_GETOLEINTERFACE,
'Ptr', 0, 'PtrP', IRichEditOle := ComValue(VT_UNKNOWN, 0))
ITextDocument := ComObjQuery(IRichEditOle, IID_ITextDocument2)
return ComValue(VT_DISPATCH, ITextDocument, F_OWNVALUE)
}
Last edited by teadrinker on 21 Nov 2023, 00:37, edited 2 times in total.
Re: rich text edit control in AHK V2?
@teadrinker somewhat of an aside but how does that work by setting properties on the text range? The ITextRange2 interface shows all methods for getters and setters of properties.
-
- Posts: 4358
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: rich text edit control in AHK V2?
I'm not quite sure what exactly you are asking. Why I use rng.Text but not rng.SetText?
Anyway, you can see all the property and method names like this:
Anyway, you can see all the property and method names like this:
Code: Select all
#DllLoad 'Msftedit.dll'
testGui := Gui()
re := testGui.AddCustom('ClassRICHEDIT50W')
TextDocument := GetDispatch(re.hwnd)
rng := TextDocument.Range(0, 0)
infoMap := EnumComMembers(rng)
wnd := Gui('Resize Owner', 'ITextRange2 Class Members')
wnd.SetFont('s11', 'Calibri')
wnd.OnEvent('Size', (g, mm, w, h) => lv.Move(,, w, h))
wnd.MarginX := wnd.MarginY := 0
headers := ['ID', 'Name', 'Description', 'Kind', 'Arg count', 'Opt args']
lv := wnd.AddListView('Grid R' . (infoMap.Count > 30 ? 31 : infoMap.Count + 1), headers)
for id, info in infoMap {
lv.Add('Vis', id, info.name, info.desc, info.kind, info.argNum, info.optArgs)
}
Loop headers.Length {
lv.ModifyCol(A_Index, 'AutoHdr' . (A_Index > 4 ? ' Center' : ''))
}
lv.OnEvent('ContextMenu', (lv, item, *) => (ToolTip('Copied: ' . A_Clipboard := lv.GetText(item, 2)), Sleep(800), ToolTip()))
wnd.Show('w900')
EnumComMembers(IDispatch) {
static VT_DISPATCH := 9, VT_UNKNOWN := 13, MEMBERID_NIL := -1
if ComObjType(IDispatch) != VT_DISPATCH {
return MsgBox('IDispatch type only supported')
}
pIDispatch := ComObjValue(IDispatch)
ComCall(GetTypeInfoCount := 3, pIDispatch, 'UIntP', &hasInfo := 0)
if !hasInfo {
return MsgBox('ITypeInfo Interface not supported')
}
ComCall(GetTypeInfo := 4, pIDispatch, 'UInt', 0, 'UInt', 0, 'PtrP', ITypeInfo := ComValue(VT_UNKNOWN, 0))
name := 'IDispatch'
Loop {
ComCall(GetTypeAttr := 3, ITypeInfo, 'PtrP', &pTypeAttr := 0)
cFuncs := NumGet(pTypeAttr, 40 + A_PtrSize, 'Short')
cImplTypes := NumGet(pTypeAttr, 44 + A_PtrSize, 'Short')
ComCall(ReleaseTypeAttr := 19, ITypeInfo, 'Ptr', pTypeAttr)
if cImplTypes {
ComCall(GetRefTypeOfImplType := 8, ITypeInfo, 'Int', 0, 'PtrP', &pRefType := 0)
ComCall(GetRefTypeInfo := 14, ITypeInfo, 'Ptr', pRefType, 'PtrP', ITypeInfo2 := ComValue(VT_UNKNOWN, 0))
ComCall(GetDocumentation := 12, ITypeInfo2, 'Int', MEMBERID_NIL, 'PtrP', &pName := 0, 'Ptr', 0, 'Ptr', 0, 'Ptr', 0)
name := StrGet(pName)
DllCall('OleAut32\SysFreeString', 'Ptr', pName)
if name != 'IDispatch' {
ITypeInfo := ITypeInfo2
}
}
} until name == 'IDispatch'
info := Map()
Loop cFuncs {
ComCall(GetFuncDesc := 5, ITypeInfo, 'UInt', A_Index - 1, 'PtrP', &pFuncDesc := 0)
id := NumGet(pFuncDesc, 0, 'Short')
invkind := NumGet(pFuncDesc, 4 + A_PtrSize * 3, 'Short')
argNum := NumGet(pFuncDesc, 12 + A_PtrSize * 3, 'Short')
optArgs := NumGet(pFuncDesc, 14 + A_PtrSize * 3, 'Short')
ComCall(ReleaseFuncDesc := 20, ITypeInfo, 'Ptr', pFuncDesc)
try ComCall(GetDocumentation := 12, ITypeInfo, 'Int', id, 'PtrP', &pName := 0, 'PtrP', &pDesc := 0, 'Ptr', 0, 'Ptr', 0)
catch {
continue
}
name := StrGet(pName), desc := ''
(pDesc && desc := StrGet(pDesc))
DllCall('OleAut32\SysFreeString', 'Ptr', pName), DllCall('OleAut32\SysFreeString', 'Ptr', pDesc)
kind := ['method', 'get',, 'put',,,, 'putref'][invkind]
if info.Has(id) && info[id].kind != 'method' && !(info[id].kind ~= '(^|,\s)' . kind . '(,|$)') {
info[id].kind .= ', ' . kind, info[id].argNum := argNum, info[id].optArgs := optArgs
} else {
info[id] := {name: name, desc: desc, kind: kind, argNum: argNum, optArgs: optArgs}
}
}
return info
}
GetDispatch(hEdit) {
static EM_GETOLEINTERFACE := 0x43C, VT_DISPATCH := 9, VT_UNKNOWN := 13, F_OWNVALUE := 1
, IID_ITextDocument2 := '{01C25500-4268-11D1-883A-3C8B00C10000}'
DllCall('SendMessage', 'Ptr', hEdit, 'UInt', EM_GETOLEINTERFACE,
'Ptr', 0, 'PtrP', IRichEditOle := ComValue(VT_UNKNOWN, 0))
ITextDocument := ComObjQuery(IRichEditOle, IID_ITextDocument2)
Return ComValue(VT_DISPATCH, ITextDocument, F_OWNVALUE)
}
-
- Posts: 4358
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: rich text edit control in AHK V2?
Perhaps for scripting languages getters and setters are overridden in properties, I don't have enough knowledge to explain what might trigger such a change.
-
- Posts: 4358
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: rich text edit control in AHK V2?
An example you are probably familiar with: IAccessible::put_accValue and IAccessible::get_accValue in AHK is simply AccObj.accValue().
Re: rich text edit control in AHK V2?
I think it’s because those interfaces all derive from iDispatch and AHK does some things in the background and tries to call or set the property. I’m sure I’m using the wrong terminology but I’ve seen Lexikos discuss it with respect to JavaScript stuff in IE
Re: rich text edit control in AHK V2?
IAccessible::get_accValue in AHK is simply AccObj.accValue[].
As far as I know, the members and calling methods of interface IDispatch are defined in the idl file.
By default, the property xx decorated with propget in idl file will generate a declaration of get_xx in the header file after compilation.
Maybe this behavior can be changed, for example, generating method names with Getxx style.
As far as I know, the members and calling methods of interface IDispatch are defined in the idl file.
By default, the property xx decorated with propget in idl file will generate a declaration of get_xx in the header file after compilation.
Maybe this behavior can be changed, for example, generating method names with Getxx style.
Re: rich text edit control in AHK V2?
@teadrinker , this is quite impressive! Thank you.
If I wanted the link to trigger an action in AHK, would that be possible, too?
And how about loading a .rtf-File into the edit element?
If I wanted the link to trigger an action in AHK, would that be possible, too?
And how about loading a .rtf-File into the edit element?
-
- Posts: 4358
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: rich text edit control in AHK V2?
Of course, I have separated the handling of clicking on a link into the OnLinkClick() function:
Code: Select all
#Requires AutoHotkey v2.0
#DllLoad 'Msftedit.dll'
styles := (ES_SAVESEL := 0x00008000) | (ES_NOHIDESEL := 0x00000100) | (ES_MULTILINE := 0x00000004)
margins := (EC_LEFTMARGIN := 1) | (EC_RIGHTMARGIN := 2)
WM_LBUTTONDOWN := 0x0201, EM_SETREADONLY := 0x00CF, EM_SETEVENTMASK := 0x0445, EM_SETMARGINS := 0x00D3
EN_LINK := 0x70B, ENM_LINK := 0x04000000, tomNullCaret := 2
imagePath := 'pic.png'
if !FileExist(imagePath) {
Download 'https://i.imgur.com/26lSSUi.png', imagePath
}
wnd := Gui(, 'RichEdit Test')
wnd.MarginX := wnd.MarginY := 15
re := wnd.AddCustom('ClassRICHEDIT50W x15 y15 w395 h250 ' . styles)
SendMessage EM_SETEVENTMASK,, ENM_LINK, re
SendMessage EM_SETMARGINS, margins, 12 | 12 << 16, re
TextDocument := GetDispatch(re.hwnd)
TextDocument.CaretType := tomNullCaret
rng := CreateRange(0, 0,, 5)
rng.Text := '`n'
rng := CreateRange(rng.End, rng.End, 'Calibri', 28, 0x38D3E2)
rng.Text := 'This is a RichEdit control'
rng := CreateRange(rng.End, rng.End,, 5)
rng.Text := '`n`n'
rng := CreateRange(rng.End, rng.End, 'Consolas', 18)
rng.Text := 'AutoHotkey'
rng.Url := '"https://www.autohotkey.com/boards/viewtopic.php?p=540158#p540158"'
rng := CreateRange(rng.End, rng.End, 'Consolas', 18)
rng.Text := ' 🠔 this is a link'
rng := CreateRange(rng.End, rng.End,, 12)
sp := ' '
Loop 9 * A_ScreenDPI // 100 {
sp .= A_Space
}
rng.Text := '`n`n`n' . sp
InsertPicture(imagePath, CreateRange(rng.End, rng.End), 2000, 2000) ; HIMETRIC := 0.01 mm
SendMessage EM_SETREADONLY, true,, re
wnd.Show()
OnLinkClick(link) => MsgBox(link)
re.OnNotify(EN_LINK, (e, lParam) => (
NumGet(lParam, A_PtrSize * 3, 'UInt') = WM_LBUTTONDOWN && (
start := NumGet(lParam, A_PtrSize * 5 + 4, 'UInt'),
end := NumGet(lParam, A_PtrSize * 5 + 8, 'UInt'),
OnLinkClick(TextDocument.Range(start, end).Text)
)
))
CreateRange(start, end, fontName := 'Verdana', fontSize := 12, fontColor?) {
rng := TextDocument.Range(start, end)
rng.Font.Name := fontName
rng.Font.Size := fontSize
IsSet(fontColor) && rng.Font.ForeColor := fontColor
return rng
}
InsertPicture(imagePath, rng, w, h) {
buf := FileRead(imagePath, 'RAW')
BSTR := DllCall('OleAut32\SysAllocString', 'Str', 'pic')
pIStream := DllCall('Shlwapi\SHCreateMemStream', 'Ptr', buf, 'UInt', buf.size, 'Ptr')
ComCall(InsertImage := 107, ComObjValue(rng), 'UInt', w, 'UInt', h, 'UInt', 1,
'UInt', TA_BASELINE := 0x18, 'Ptr', BSTR, 'Ptr', pIStream)
ObjRelease(pIStream)
}
GetDispatch(hEdit) {
static EM_GETOLEINTERFACE := 0x43C, VT_DISPATCH := 9, VT_UNKNOWN := 13, F_OWNVALUE := 1
, IID_ITextDocument2 := '{01C25500-4268-11D1-883A-3C8B00C10000}'
DllCall('SendMessage', 'Ptr', hEdit, 'UInt', EM_GETOLEINTERFACE,
'Ptr', 0, 'PtrP', IRichEditOle := ComValue(VT_UNKNOWN, 0))
ITextDocument := ComObjQuery(IRichEditOle, IID_ITextDocument2)
Return ComValue(VT_DISPATCH, ITextDocument, F_OWNVALUE)
}
I haven't tested it yet, but I don't think there should be a problem with it. There is the ITextDocument::Open method.