Jump to content


Photo

[CLASS] SysLink_Control - Support for SysLink controls


  • Please log in to reply
17 replies to this topic

#1 just me

just me
  • Members
  • 1175 posts

Posted 11 September 2011 - 09:38 AM

Obsolete since AHK 1.1.06 introduced a built-in Link control!


majkinetor has done it before and as HLink it is a part of his Forms Framework. Nevertheless I've written this class. The main advantage is, that it adjusts the controls height for a given width automatically. For all other details have a look at the inline documentation, please.

Class_SysLink_Control.ahk:
; ======================================================================================================================
; AHK 1.1.04 +
; ======================================================================================================================
; Function:          Support for SysLink controls - http://msdn.microsoft.com/en-us/library/bb760704%28VS.85%29.aspx
;                    They may be useful in some cases, but they offer very few options for customizing.
; AHK version:       1.1.04.00 (U32)
; Language:          English
; Tested on:         Win XPSP3, Win VistaSP2 (32 Bit)
; Version:           0.0.02.03/2011-10-08/just me
; How to use:        To create a new Syslink control use MySysLink := New SysLink_Control()
;                    passing up to four parameters:
;                       HGUI      - HWND of the GUI                                          (Pointer)
;                       Content   - Text to show in the control                              (String)
;                       ----------- Optional ---------------------------------------------------------
;                       Options   - Options for positioning and sizing (X, Y, W, H)          (String)
;                                   as used in "Gui, Add" command. 
;                                   Defaults: X - automatically positioned by GUI
;                                             Y - automatically positioned by GUI
;                                             W - 200
;                                             H - 0 (the height will be adjusted to fit the text)
;                                   The options "BackgroundTrans" and "Border" are supported also.
;                       Function  - Function to be called on WM_NOTIFY messages              (String)
;                                   Default: "_SysLink_Notifications"
;                    On success   - New returns an object with seven public keys:
;                       HWND      - HWND of the control                                      (Pointer)
;                       X         - X-position                                               (Integer)
;                       Y         - Y-position                                               (Integer)
;                       W         - Width of the control                                     (Integer)
;                       H         - Height of the control                                    (Integer)
;                       ID        - Id of the control (>= 0x4000)                            (Integer)
;                       DUMMY     - HWND of the GUI text control                             (Pointer)
;                    On failure   - New returns False, ErrorLevel contains additional informations.
;
;                    To destroy the control afterwards simply assign an empty string or zero (e.g. MySysLink := "").
;
; Remarks:           MSDN: "The SysLink control supports the anchor tag(<a>) along with the attributes HREF and ID.
;                    An HREF can be any protocol, such as http, ftp, and mailto. An ID is an optional name,
;                    unique within a SysLink control, and it is associated with an individual link.
;                    Links are also assigned a zero-based index according to their position within the string."
;
;                    The scripts provides the function _SysLink_Notifications() as default handler for
;                    NM_CLICK and NM_RETURN notifications (the only notifications associated with the control).
;                    This function only will run clicked URLs. If you want to do another action or to respond also
;                    to clicks on IDs, you have to pass the name of a function accepting exactly four parameters
;                    (Hwnd, Msg, wParam, lParam) to be called instead.
;
;                    This class is using Class_Notification.ahk from http://www.autohotkey.com/forum/topic75591.html
;                    because I think it makes things easier. If you want to handle other notifications in your script,
;                    you should use it too.
; ======================================================================================================================
; This software is provided 'as-is', without any express or implied warranty.
; In no event will the authors be held liable for any damages arising from the use of this software.
; ======================================================================================================================
#include Class_Notification.ahk     ; http://www.autohotkey.com/forum/topic75591.html
; ======================================================================================================================
Class SysLink_Control {
   ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   ; PRIVATE Properties and Methods ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   ; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   ; ===================================================================================================================
   ; CONSTRUCTOR     __New()
   ; ===================================================================================================================
   __New(HGUI, Content, Options = "", Function = "") {
      Global Notification
      Static DefaultFunc := "_SysLink_Notifications"
      Static INIT := False
      Static HDEFFONT := 0
      Static DEFAULT_GUI_FONT := 17
      Static ICC_LINK_CLASS := 0x00008000
      Static IDC_SYSLINK := 0x4000
      Static WC_LINK    := "SysLink"
      Static LWS_TRANSPARENT := 0x0001
      Static WM_USER := 0x400
      Static LM_GETIDEALHEIGHT := (WM_USER + 0x301)
      Static WM_SETFONT := 0x0030
      Static WM_GETFONT := 0x0031
      Static NM_FIRST   := 0
      Static NM_CLICK   := (NM_FIRST-2)
      Static NM_RETURN  := (NM_FIRST-4)
      Static WS_TABSTOP := 0x00010000
      Static WS_BORDER  := 0x00800000 
      Static WS_VISIBLE := 0x10000000
      Static WS_CHILD   := 0x40000000
      Static DefaultW := 200
      Static DefaultH := 0
      If !(INIT) {
         VarSetCapacity(ICCX, 8, 0)
         NumPut(8, ICCX, 0, "UInt")
         NumPut(ICC_LINK_CLASS, ICCX, 4, "UInt")
         If !DllCall("Comctl32.dll\InitCommonControlsEx", "Ptr", &ICCX)
            Return False
         HDEFFONT := DllCall("Gdi32.dll\GetStockObject", "Int", DEFAULT_GUI_FONT)
         INIT := True
      }
      If !DllCall("User32.dll\IsWindow", "UPtr", HGUI) {
         ErrorLevel := "Invalid parameter HGUI: " . HGUI
         Return False
      }
      If (Function <> "") {
         If !IsFunc(Function) || (Func(Function).MaxParams <> 4) {
            ErrorLevel := "Invalid parameter NotifyFunc: " . Function
            Return False
         }
      } Else {
         Function := DefaultFunc
      }
      W := DefaultW
      H := DefaultH
      Opts := {X: "", Y: "", W: "", H: ""}
      Options := Trim(RegExReplace(Options, "\s+", " "))
      SL_STYLES := WS_TABSTOP | WS_VISIBLE | WS_CHILD
      Loop, Parse, Options, %A_Space%
      {
         If (A_LoopField = "BackgroundTrans") {
            SL_STYLES |= LWS_TRANSPARENT
            Continue
         }
         If (A_LoopField = "Border") {
            SL_STYLES |= WS_BORDER
            Continue
         }
         O := SubStr(A_LoopField, 1, 1)
         If InStr("XYWH", O)
            Opts[O] := A_LoopField
      }
      If (Opts.W <> "")
         W := Opts.W
      If (Opts.H <> "")
         H := Opts.H
      Options := Opts.X . " " . Opts.Y . " " . Opts.W . " " . Opts.H
      Gui, %HGUI%:Add, Text, %Options% hwndHDUMMY, X
      GuiControlGet, CP, Pos, %HDUMMY%
      HFONT := DllCall("SendMessage", "Ptr", HDUMMY, "Int", WM_GETFONT, "Ptr", 0, "Ptr", 0, "UPtr")
      If !(HFONT)
         HFONT := HDEFFONT
      X := CPX
      Y := CPY
      If (W = Opts.W)
         W := CPW
      If (H = Opts.H)
         H := CPH
      HSL := DllCall("CreateWindowExW", "UInt", 0, "WStr", WC_LINK, "WStr", Content, "UInt", SL_STYLES
                    , "Int", X, "Int", Y, "Int", W, "Int", H
                    , "Ptr", HGUI, "Ptr", IDC_SYSLINK, "Ptr", 0, "Ptr", 0, "Ptr")
      If ((ErrorLevel) || !(HSL)) {
         ErrorMsg := "Couldn't create SysLink control!`nErrorLevel: " . ErrorLevel . " - HWND: " . HSL
         ErrorLevel := ErrorMsg
         Return False
      }
      DllCall("SendMessage", "Ptr", HSL, "Int", WM_SETFONT, "Ptr", HFONT, "Ptr", False)
      If (H = DefaultH) {
         H := DllCall("SendMessage", "Ptr", HSL, "Int", LM_GETIDEALHEIGHT, "Ptr", W, "Ptr", 0)
         DllCall("MoveWindow", "Ptr", HSL, "Int", X, "Int", Y, "Int", W, "Int", H, "Int", False)
         If (H <> CPH)
            Gui, %HGUI%:Add, Text, x%X% y%Y% w%W% h%H% hwndHDUMMY +BackgroundTrans
      }
      This.HWND := HSL
      This.X := X
      This.Y := Y
      This.W := W
      This.H := H
      This.ID := IDC_SYSLINK
      This.DUMMY := HDUMMY
      IDC_SYSLINK += 1
      Notification.Register(HSL, NM_CLICK, Function, "N")
      Notification.Register(HSL, NM_RETURN, Function, "N")
   }
   ; ===================================================================================================================
   ; DESTRUCTOR      __Delete()
   ; ===================================================================================================================
   __Delete() {
      Global Notification
      Static NM_FIRST  := 0
      Static NM_CLICK  := (NM_FIRST-2)
      Static NM_RETURN := (NM_FIRST-4)
      If (This.HWND) {
         Notification.Unregister(This.HWND, NM_CLICK)
         Notification.Unregister(This.HWND, NM_RETURN)
         DllCall("DestroyWindow", "Ptr", This.HWND)
      }
   }
}
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; PRIVATE Functions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; ======================================================================================================================
_SysLink_Notifications(Hwnd, Msg, wParam, lParam) {
   ; lParam is a pointer to a NMLINK structure containing NMHDR and LITEM structures
   ; -> http://msdn.microsoft.com/en-us/library/bb760714%28VS.85%29.aspx
   Static NM_FIRST  := 0
   Static NM_CLICK  := (NM_FIRST-2)
   Static NM_RETURN := (NM_FIRST-4)
   Static MAX_LINKID_TEXT := 48
   Static L_MAX_URL_LENGTH := (2048 + 32 + 3) ; 3 : sizeof("://"))
   Static OffHwnd   := 0
   Static OffID     := OffHwnd + A_PtrSize
   Static OffCode   := OffID + A_PtrSize
   Static OffLITEM  := OffCode + 4
   Static OffMask   := OffLITEM
   Static OffItem   := OffMask + 4
   Static OffState  := OffItem + 4
   Static OffStmask := OffState + 4
   Static OffID     := OffStmask + 4
   Static OffUrl    := OffID + (MAX_LINKID_TEXT * 2)
   Url := StrGet(lParam + OffUrl, L_MAX_URL_LENGTH, "UTF-16")
   If (Trim(URl))
      Run, *Open %Url%
}
; ======================================================================================================================
Small example:
#NoEnv
#Include Class_SysLink_Control.ahk
Gui, New, +hwndHGUI +OwnDialogs, SysLink GUI
Gui, Margin, 20, 20
Gui, Color, 0xE0E0E0
Gui, Font, s12 q5, Courier New
TXT := "For more information about AutoHotkey <A HREF=""http://www.autohotkey.com/forum"">visit the forum</A>, please."
SLC := New SysLink_Control(HGUI, TXT, "w300 BackgroundTrans Border")
If !IsObject(SLC) {
   MsgBox, Couldn't create SysLink control!`n%ErrorLevel%
   ExitApp
}
Gui, Font
Gui, Font, s10 q5
Gui, Add, Edit, wp, Automatically positioned Edit!
Gui, Show
Return
GuiClose:
ExitApp
:!: SysLink controls are using Unicode. I didn't test it with an ANSI version so I don't know whether the script will run flawlessly in this environment.


New version on 2011-09-16:[*:21kx25k0]Note: Requires AHK 1.1.04+
[*:21kx25k0]Changed positioning and sizing to use an option string supporting common GUI options, so positioning and sizing will be done as expected for common GUI controls. In addition a transparent text control is added above the SysLink control to make GUI aware of the control.
[*:21kx25k0]Added new options BackgroundTrans and Border
[*:21kx25k0]Changed control's default font to default GUI font
[*:21kx25k0]Added new parameter Font to pass a font description string supporting the common Gui, Font syntax for size, weight, quality, and name.
[*:21kx25k0]Still only styles valid for WinXP are supported.Update on 2011-09-16: Sometimes things look more complicate than they are:[*:21kx25k0]Removed parameter font.
[*:21kx25k0]Changed controls's default behavior to use the current GUI font.
[*:21kx25k0]Updated script and sample.Update on 2011-10-09:[*:21kx25k0]Added new property DUMMY to yield the HWND of the associated GUI text control.

#2 fragman

fragman
  • Members
  • 1591 posts

Posted 11 September 2011 - 11:55 AM

Very nice. Are you interested in integrating it into my OOP GUI library?
It should actually be pretty easy, just needs some message rerouting and smaller adjustments.

#3 just me

just me
  • Members
  • 1175 posts

Posted 11 September 2011 - 01:44 PM

If you want to do it, just do it.
And if you want me to make any necessary adjustments, just tell me what to do. :wink:

#4 fragman

fragman
  • Members
  • 1591 posts

Posted 11 September 2011 - 03:48 PM

I'm not quite sure yet how to implement it best. Right now I'm only supporting controls which are also supported by AHK. I think the difficulties would mostly involve initial positioning of the control (I'm just using the options string used in Gui, Add right now) and some event rerouting.

I would like to see it integrated, but I don't know yet when I will find time myself to do so, so if you're interested, feel free to have a try at it and let me know about any possible questions.

As I said in the other thread I'm going to have to integrate some window message rerouting so it's probably best to use that when it's ready.

#5 just me

just me
  • Members
  • 1175 posts

Posted 16 September 2011 - 07:03 AM

*new version*

#6 linkman

linkman
  • Guests

Posted 16 September 2011 - 07:50 AM

Nice job just me! :D

Could you please make antialiased font rendering to work with SysLink control? Is this possible? If I put
Gui, Font, q5
in your example, Edit control's font is antialiased, but SysLink control's font isn't ...

Thanks. (My request in Wish List here.)

#7 just me

just me
  • Members
  • 1175 posts

Posted 16 September 2011 - 09:42 AM

THX for your note, linkman. Trying around solved the "underlining" issue. I deleted the font object at the wrong time.

Rendering quality was supported already, if the q option was included in the font parameter.

I've updated the script and the sample. :wink:

#8 linkman

linkman
  • Guests

Posted 16 September 2011 - 10:59 AM

Thank you! :D

#9 just me

just me
  • Members
  • 1175 posts

Posted 16 September 2011 - 07:39 PM

*another update*

#10 fragman

fragman
  • Members
  • 1591 posts

Posted 17 September 2011 - 08:48 AM

Very nice. Supporting an options string for sizes will help to integrate it in CGUI library.

#11 fragman

fragman
  • Members
  • 1591 posts

Posted 05 October 2011 - 08:01 PM

I tried integrating it in the CGUI library and it's mostly working fine, but I'm having trouble getting the URL from a WM_NOTIFY message:
OnWM_NOTIFY(wParam, lParam) { 
		; lParam is a pointer to a NMLINK structure containing NMHDR and LITEM structures 
		; -> http://msdn.microsoft.com/en-us/library/bb760714%28VS.85%29.aspx 
		Static NM_FIRST  := 0 
		Static NM_CLICK  := (NM_FIRST-2) 
		Static NM_RETURN := (NM_FIRST-4) 
		Static MAX_LINKID_TEXT := 48 
		Static L_MAX_URL_LENGTH := (2048 + 32 + 3) ; 3 : sizeof("://")) 
		Static OffHwnd   := 0 
		Static OffIDFrom     := OffHwnd + A_PtrSize 
		Static OffCode   := OffIDFrom + A_PtrSize 
		Static OffLITEM  := OffCode + 4 
		Static OffMask   := OffLITEM 
		Static OffItem   := OffMask + 4 
		Static OffState  := OffItem + 4 
		Static OffStmask := OffState + 4 
		Static OffID     := OffStmask + 4 
		Static OffUrl    := OffID + (MAX_LINKID_TEXT * 2)
		if(wParam = NM_CLICK || wParam = NM_RETURN)
		{
			Url := StrGet(lParam + OffUrl, L_MAX_URL_LENGTH, "UTF-16") 
			Url := Trim(Url)
			if(!this.CallEvent("Click", Url).Handled && Url)
				Run, *Open %Url% 
		}
	}
This code is somewhat modified from the original handling function, but should be fine from what I know. wParam is always 16384 though instead of -2 or -4 and I can't seem to extract a valid URL either.

#12 just me

just me
  • Members
  • 1175 posts

Posted 06 October 2011 - 03:28 PM

For a WM_NOTIFY message wParam contains the control identifier, i.e. 0x4000 (16384) for the first SysLink control. The special notification message can be found in the code member of the NMHDR structure lParam points to (lParam + OffCode) :arrow: http://msdn.microsof...714(VS.85).aspx

#13 fragman

fragman
  • Members
  • 1591 posts

Posted 07 October 2011 - 03:17 PM

You're right of course, I confused some parameters :(

I got it working now and will add it to the library. Adding the IPAddress control is pretty simple once I know that this control is working properly.

Do you know if the dummy control needs to be resized when the size of this control changes? Or does AHK only consider the initial position of a control?

#14 just me

just me
  • Members
  • 1175 posts

Posted 08 October 2011 - 06:46 AM

For common actions as minimizing, maximizing, and resizing the size of the dummy control doesn't matter, but it does when you use the AutoSize option, if one of the borders depends on the dummy control.

#15 fragman

fragman
  • Members
  • 1591 posts

Posted 08 October 2011 - 11:09 AM

Ok, I might have to overwrite the position/size related code in the control then.