Page 1 of 1

Simple GDI Class

Posted: 05 Jan 2015, 17:14
by GeekDude
A class aiming to make using low-level GDI functions simple.

Example use code: Simple button
Image

Code: Select all

#Include GDI.ahk

Gui, Margin, 50, 50
Gui, Add, Progress, w100 h50 hWndhWnd ; Progress controls make ideal canvases
Gui, Show

global MyButton := new CustomButton(hWnd)
OnMessage(0xF, "WM_PAINT")
OnMessage(0x200, "WM_MOUSEMOVE")
OnMessage(0x201, "WM_LBUTTONDOWN")
OnMessage(0x202, "WM_LBUTTONUP")
return

GuiClose:
ExitApp

WM_PAINT()
{
	Sleep, -1 ; Let the scrollbar redraw before painting over it
	MyButton.BitBlt()
}

WM_MOUSEMOVE(wParam, lParam, Msg, hWnd)
{
	static LasthWnd
	if (hWnd != LasthWnd)
	{
		if (hWnd == MyButton.hWnd)
			MyButton.Hovering()
		else if (lasthWnd == MyButton.hWnd)
			MyButton.Default()
		LasthWnd := hWnd
	}
}

WM_LBUTTONDOWN(wParam, lParam, Msg, hWnd)
{
	if (hWnd == MyButton.hWnd)
		MyButton.Held()
}

WM_LBUTTONUP(wParam, lParam, Msg, hWnd)
{
	if (hWnd == MyButton.hWnd)
		MyButton.Hovering()
}

class CustomButton
{
	__New(hWnd)
	{
		this.GDI := new GDI(hWnd)
		this.hWnd := hWnd
		this.Default()
	}
	
	Default()
	{
		this.Draw("Hover", 0xFFFFFF, 0x000000)
	}
	
	Hovering()
	{
		this.Draw("Click", 0xC0C0C0, 0x000000)
	}
	
	Held()
	{
		this.Draw("Release", 0x000000, 0x0000FF)
	}
	
	Draw(Text, Color, TextColor)
	{
		critical
		this.GDI.FillRectangle(0, 0, this.GDI.CliWidth, this.GDI.CliHeight, Color, TextColor)
		this.GDI.DrawText(0, 0, 100, 50, Text, TextColor, "Courier New", 20, "CC")
		this.GDI.BitBlt()
	}
	
	BitBlt()
	{
		this.GDI.BitBlt()
	}
}
The library code:

Re: Simple GDI Class

Posted: 05 Jan 2015, 17:24
by joedf
Nice!

Re: Simple GDI Class

Posted: 07 Jan 2015, 22:56
by Soft
oh.. nice

Re: Simple GDI Class

Posted: 07 Jan 2015, 22:57
by GeekDude
"oh.."?

Re: Simple GDI Class

Posted: 18 Jan 2015, 11:43
by vasili111

Re: Simple GDI Class

Posted: 18 Jan 2015, 12:49
by GeekDude
It's not really beautiful, but I'll post it anyways if you want me to

Re: Simple GDI Class

Posted: 19 Jan 2015, 00:00
by joedf
But! It will facilitate new GUIs using GDI

Re: Simple GDI Class

Posted: 19 Jan 2015, 22:34
by dfdfdf
where is GDI.ahk?

Re: Simple GDI Class

Posted: 19 Jan 2015, 22:38
by joedf

Re: Simple GDI Class

Posted: 20 Jan 2015, 01:48
by sddf
thanks

Re: Simple GDI Class

Posted: 14 Apr 2016, 23:29
by lblb
Hi Geekdude,

This is cool and simple! Thanks for your work.

Is there any way that this could be adapted to make circular buttons? There are ellipse functions in Tic's Gdip library so I'm guessing it may be possible, but adding it to your class is way over my head. Thanks in advance for the help you may be able to provide.

Re: Simple GDI Class

Posted: 14 Apr 2016, 23:39
by GeekDude
Greetings lblb,

You should be able to take the code for FillRectangle and modify it to use a DllCall for Ellipse instead of for Rectangle. It looks like it takes all the same parameters as Rectangle, so it should be as simple as copying that class method and changing the name Rectangle to Ellipse. I'd probably also remove that 1x1 pixel check, since it doesn't really apply to an ellipse in the same way it might a rectangle.

Here is the documentation page for Ellipse

Re: Simple GDI Class

Posted: 14 Apr 2016, 23:52
by lblb
Hi Geekdude,

Thanks a lot for the quick reply and for the info, much appreciated.

I'm willing to work on it but this is quite new to me. So before I invest some time into this, maybe I should ask if you think it would be possible to reach my objective. What I am trying to do is to have individual floating circle buttons that react as you showed in your test script. So I would make the Gui background transparent in order to only see the circle buttons (and have some free space between the buttons). If I can come up with the Ellipse code, I'm not sure what to expect: since the Fill functions seem to act by changing the color of the progress control, would I be stuck with the corner of the rectangular progress control sticking out of the ellipse shape?

Re: Simple GDI Class

Posted: 15 Apr 2016, 03:11
by lblb
Hi Geekdude,

Thanks a lot for your help. I think I'm getting close to what I need. Here is the code I have so far:
Spoiler
And then in GDI.ahk, I replaced FillRectangle with:
Spoiler
When you run the script above, there should be two round buttons at the middle of the screen.

A few questions:


1) I made the default state of the buttons transparent by using

Code: Select all

Winset, Transcolor, 0x000000
It was necessary to use 0x000000 for Transcolor (and for the Gui background color) because the corners of the progress control that are outside of the ellipse are black. Is there any way to control the color of these corners outside of the ellipse?


2) As you can see, the edges of the circle and of the text are a bit jagged. In Tic's Gdip library, it's possible to adjust the antialiasing using the Quality functions. Would it be possible to do the same here? I have no clue how I could include this function in your code.
Spoiler

3) I'm trying to put different text on each button. That works well by itself using something like:

Code: Select all

Default()
	{
	global LasthWnd
	global hwnd
	if (hWnd == MyButton.hWnd)
			this.Draw("Hover1", 0x000000, 0x00000F)
	Else if (hWnd == MyButton2.hWnd)
			this.Draw("Hover2", 0x000000, 0x00000F)
	}
But I'm running in some issues when I combine this with the function to reset the buttons when the cursor leaves the buttons. When the cursor leaves the the buttons, I use the following to reset the buttons:

Code: Select all

OnMessage(0x2A2, "WM_NCMOUSELEAVE")
SetTimer, CheckIfMouseIsOutsideGui,10
If you hover on and off over button 1 (or button 2), the text is updated correctly. But if you hover over button 1, then go over button 2, the first time you hover over button 2 the hover text is not updated correctly. Can you see where the mistake is?


Thanks a lot for all your help. This is really challenging for me as I'm not used to working with functions and classes (so for example, I'm not really sure the WM_NCMOUSELEAVE and CheckIfMouseIsOutsideGui are deployed correctly) but I'm learning a whole lot. So thanks in advance for any support you may be able to offer.

Re: Simple GDI Class

Posted: 19 Apr 2016, 09:34
by Guest
I'm running into some issues with multiple buttons too...
It seems they get switched around for some reason. Can't pinpoint why yet.
There seems to be some HWND weirdness going on.

Could you provide an example of using more than one button Geekdude? Thanks.
Like this it seems kinda confusing to get things right. (I usually don't work with HWNDS to get things done)

Re: Simple GDI Class

Posted: 20 Apr 2016, 12:16
by GeekDude
Sorry for the late replies. Here's some code with multiple round buttons. I took advantage of some newer AHK features that let me handle the messages for each button individually instead of having a single message handler that handles all the buttons. Also, I fixed WM_MOUSELEAVE (had to do some research for this one, and modified it to be both 32 and 64 bit compatible). See the original post for the updated GDI.ahk with FillEllipse.

As for making things smoother/less pixelated, it's not really possible. My class strictly uses GDI (not plus) functions, which don't have such features. That kind of functionality is only present in GDI+. If you want good transparency and aliasing (smoothing), you'll need to use either tic's gdip or his gdip2 beta (also class based). https://github.com/tariqporter/Gdip/blo ... /Gdip2.ahk

Code: Select all

#NoEnv
SetBatchLines, -1

#Include <GDI>

; Window Message Constants
global WM_PAINT := 0xF
, WM_MOUSEMOVE := 0x200
, WM_LBUTTONDOWN := 0x201
, WM_LBUTTONUP := 0x202
, WM_MOUSELEAVE := 0x2A3

; TRACKMOUSEEVENT struct for WM_MOUSELEAVE
; https://autohotkey.com/board/topic/32663-/?p=207708
VarSetCapacity(TME, 24, 0)
NumPut(8+A_PtrSize*2, TME, 0, "UInt") ; cbSize
NumPut(2, TME, 4, "UInt") ; dwFlags
NumPut(0, TME, 8, "UPtr") ; hwndTrack
NumPut(0, TME, 8+A_PtrSize, "UInt") ; dwHoverTime

Buttons := []
Loop, 5
{
	Gui, Add, Progress, w150 h100 hWndhWnd
	Buttons.Push(new CustomButton(hWnd))
}
Gui, Show
return

GuiClose:
Gui, Destroy

; Erase all button references so the class instances can be deleted
for Index, Button in Buttons
	Button.Release()
Button := "" ; "Button" from the for loop actually still exists, so delete that reference too
Buttons := ""

ExitApp
return

class CustomButton
{
	Messages := [WM_PAINT, WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSELEAVE]
	
	__New(hWnd)
	{
		this.hWnd := hWnd
		this.GDI := new GDI(hWnd)
		
		; Fill in background
		this.GDI.FillRectangle(0, 0, this.GDI.CliWidth, this.GDI.CliHeight, 0xF0F0F0)
		
		; Fill in foreground
		this.Default()
		
		; Listen for messages
		this.Bound := this.OnMessage.Bind(this)
		for Index, Msg in this.Messages
			OnMessage(Msg, this.Bound)
	}
	
	Release()
	{
		for Index, Msg in this.Messages
			OnMessage(Msg, this.Bound, 0)
		this.Bound := "" ; Erase circular reference
	}
	
	OnMessage(wParam, lParam, Msg, hWnd)
	{
		global TME
		
		; If the/a window needs repainted
		if (Msg == WM_PAINT)
		{
			Sleep, -1
			this.BitBlt()
		}
		
		; From this point on, only handle messages specifically for this control
		if (hWnd != this.hWnd)
			return
		
		if (Msg == WM_MOUSEMOVE) {
			NumPut(hWnd, TME, 8, "Ptr") ; hwndTrack
			DllCall("TrackMouseEvent", "Ptr", &TME)
			
			; TODO: only call on state change
			if !GetKeyState("LButton")
				this.Hovering()
		} else if (Msg == WM_MOUSELEAVE)
			this.Default()
		else if (Msg == WM_LBUTTONDOWN)
			this.Held()
		else if (Msg == WM_LBUTTONUP)
			this.Hovering()
	}
	
	Default()
	{
		this.Draw("Hover", 0xFFFFFF, 0x000000)
	}
	
	Hovering()
	{
		this.Draw("Click", 0xC0C0C0, 0x000000)
	}
	
	Held()
	{
		this.Draw("Release", 0x000000, 0x0000FF)
	}
	
	Draw(Text, Color, TextColor)
	{
		this.GDI.FillEllipse(0, 0, this.GDI.CliWidth, this.GDI.CliHeight, Color, TextColor)
		this.GDI.DrawText(0, 0, this.GDI.CliWidth, this.GDI.CliHeight, Text, TextColor, "Comic Sans", 20, "CC")
		this.GDI.BitBlt()
	}
	
	BitBlt()
	{
		this.GDI.BitBlt()
	}
}

Re: Simple GDI Class

Posted: 20 Apr 2016, 13:18
by Guest
Cheers :beer:

I managed to get things looking smooth enough with your library so no need for gdip on my end.

Now if only i could find some bass.dll help i'd be set :lol: lol

Re: Simple GDI Class

Posted: 25 Apr 2016, 01:53
by lblb
Hi GeekDude,

Sorry for the late reply as I just got back into this. Thanks a lot for modifying the GDI.ahk and for modifying my script. I really appreciate it. I'll look into what you wrote and will write back here if I encounter any issues. Thanks a lot!