Jump to content
Tuts 4 You

windows calculator's color-text button


Recommended Posts

I use spy++ to check the button style of Calculator's color-text buttons.

They are not BS_OWNERDRAW.

How does microsoft change their colors?

Does any buddy know it?

OWNERDRAW buttons don't dress XP-style skin.

Link to comment
  • 1 month later...

I use spy++ to check the color button's style, it has WS_EX_NOPARENTNOTIFY.

Therefore, Dialog will not deal with WM_NOTIFY message.

I think Microsoft subclass these buttons, because they are not owner-draw.

However, how to subclass buttons' WM_PAINT message and keep XP style looking?

Link to comment

thanks for the code! that calculator is nice, Pity they never realised this and increased the size of the operators!

WM_CTLCOLORBUTTON was last operational in win95. now you would need to create your own control or use a system wide hook to catch and modify the standard button, I've done it with MessageBox but never tried it with Buttons...


Link to comment
  • 2 weeks later...

Ah HAH!!!!!!!

Got the little bugger.

Check out this code, it's where the button text gets it's colour.

					 010061D9	PUSH EAX												 ;  Color = <LIGHTMAGENTA>
010061DA PUSH DWORD PTR DS:[ESI+10] ; |hDC]
010061DD CALL NEAR DWORD PTR DS:[<&GDI32.SetTextC> ; SetTextColor
010061E8 PUSH EAX ; |pRect
010061E9 PUSH -1; ; Count = FFFFFFFF (-1.)
010061EB PUSH [ARG.4] ; Text
010061F1 CALL NEAR DWORD PTR DS:[<&USER32.DrawTex> ; DrawTextW

If you change the value in eax before the call to SetTextColor, you will change the colour of the text on whichever button is being processed at the time.

Curiously enough, I noticed that it seems to be in response to a WM_NOTIFY message.

If you position olly and calc so that they're both visible on screen without obscuring one-another, you can observe the effect of changing the value in eax that controls the text colour of the button.

Here's the result of such a diddling of the value in eax..

enjoy B)

[ EDIT ]

Here, just for kicks - it's the (very) roughly decompiled C code for the wndProc of Calc.exe

You'll notice that the SetTextColor & DrawTextW functions shown in the asm snippet are the ones found at lines

195 & 196

An interesting point is that the coloured text on the buttons is NOT the text shown on the dialog resources in the exe file!!!

I initially thought that all of the strings in Calc.exe were simply drawn over the top of the (default) black writing. I changed one of the strings and lo-and-behold...... The coloured text changed too - but there was no black 'shadow' underneath..

I may have a look later at a small demo of the idea.



Edited by enhzflep
Link to comment


You resolved my question. Thank you very much.

BTW, how you convert ASM to C code so neatly?

Link to comment

You resolved my question. Thank you very much.

BTW, how you convert ASM to C code so neatly?

Hey alaphate,

Glad I could help - it's been bugging me for 6 weeks now.......

As for the conversion from ASM to C code, Perhaps you can find a copy of IDA Pro Advanced complete with the HexRays decompiler plugin....

These products:

IDA Pro Advanced

Hex-Rays decompiler plugin for IDA Pro

Perhaps these links will be helpful?

IDA Pro search results

IDA Pro 5.2 inc decompiler (win, linux, macos)

Link to comment


Awesome! I'll try this tool.

I appreciate you help very much.

You are an excellent professor!

Link to comment

laphate, you're too kind :blush:

Went back for another, closer look - something about the windowProc where the text color is changed.

Of course! It hit me - the function was able to return 0, without exiting the program. When you return

0 after handling a call to dialogFunc, the dialog is exited. This got me thinking - if the work was done in a

window func and not a dialog func, wouldn't that have to mean that we're dealing with a regular window

and not a dialog????

So I had a closer look - it seems that the program loads all of the strings in the string-table, and registers

a new window class. It also goes through the dialog and gets the position and sze of a whole bunch of child

windows. Too tired to be bothered looking right into it right now, but it seems that the program has a dialog

in it's resource section. This dialog is *copied* and recreated as a window, thus giving the best of both worlds - an

easy to lay out program (a resource-editor drawn dialog box), with the added functionality of a frame-based program.

Quite a neat trick. That also helps explain the strings for all of the buttons that are contained both on the buttons

and also in the string table.

I've written a small program that mimics the behaviour of calc.exe

I'm _really_ sorry about the horrible code inside the WM_NOTIFY case. I just can't figure out what the structure is.

Being pointed to by the lParam, and in response to a WM_NOTIFY message we should expect to see a NMHDR structure.

In some cases the lParam points to a much larger structure, which has a NMHDR at the start. I guess we can only guess

what some of the referenced members are. Some are reasonably obvious (like the HDC), but others have me a little more stumped.

Apologies for the hard-coded button-array, I'm too lazy to look up how to create wide strings on the fly just this sec.

Otherwise, I'd have dynamically allocated the button array and generated the text labels at the same time as I generate

the text colour.

Thanks for the great challenge...


I just couldn't bear to see all of those nasty *(DWORD *)(v6 + 12); bits and pieces - I just had to neaten it up

a bit. I know the following code makes things a lot clearer for me..

#pragma push(1)
typedef struct tagBNMHDR
HWND hwndFrom;
UINT idFrom;
UINT code;
DWORD iBkMode;
HDC hdc;
RECT rect;
DWORD unknownDword;
byte unknownByte;
#pragma pop(1) /* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
switch (message) /* handle the messages */
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
LPARAM v6 = lParam; int btnId = (int) wParam;
LPNMHDR pnmh = (LPNMHDR) lParam;
LPBNMHDR header = (LPBNMHDR) lParam; int oldBkMode, resultCode;
char useOurCols;
COLORREF textColor; if ( header->code != -12 || !isCtrlButton(btnId) )
return 0; // signed int v7 = *(DWORD *)(v6 + 12);
signed int bkMode = header->iBkMode;
if ( bkMode == 3 )
resultCode = 32;
} else
if ( bkMode != 1 )
return 0; oldBkMode = SetBkMode(header->hdc, bkMode);
useOurCols = (header->unknownByte & 4) == 0; if ( useOurCols)
textColor = getButtonTextColor(btnId); else
GetSysColor(17); SetTextColor(header->hdc, textColor); DrawTextW(
); SetBkMode(header->hdc, oldBkMode);
resultCode = 4;
return resultCode;
} default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
} return 0;



Edited by enhzflep
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Create New...