Sign in to follow this  
BrickInTheWall

C - Win32 - managing custom buttons.

Recommended Posts

BrickInTheWall    150
Hi guys, I'm in a bit of a pickle here on how to continue. I'm writing a Win32 application and want to program everyting C-style and without using resources. It's working well so far. I have an owner drawn button (BS_OWNDERDRAW style)...drawing it works fine. These buttons will represent points in a coordinate system that the user can drag around. Also, I want the user to be able to add new points(buttons) whenever and wherever he wants. This is all no problem, but I'm really not sure how I should store all of these buttons. Also, every new button would need an individual ID wouldn't it? Otherwise if two buttons had the same ID and I were to intercept a "drag" action, I'd be moving both buttons eventhough only one is clicked. I was thinking of maybe using a linked list. I could try to generate ID's by incrementing a variable each time the user creates a point. If I have 10 points-> 1,2,3,4,5,6,7,8,9,10 ...if the user deletes no. 5-> 1,2,3,4,5 (formerly 6), 6 (formerly 7) etc... Any advice is appreciated. Cheers!

Share this post


Link to post
Share on other sites
enigmatix    144
I'm not sure how you structure your controls and such, and by that I mean, whether the buttons sit directly inside the window, or if they sit inside a parent control, but a more ideal solution I think would be to use what's built into the Windows API already. That is, EnumChildWindows and EnumChildProc. If you use these on the parent control, you could generate a list quickly or perform actions on the buttons themselves.

Share this post


Link to post
Share on other sites
BrickInTheWall    150
Thanks, thats definately a good piece of help when handling messages that apply to all buttons at once. But my buttons positions don't depend on how big the window is for example. If a button needs to be redrawn, I'll get a WM_DRAWITEM message but still need to figure out which button it is (i.e. get it's ID) and then associate this ID with the corresponding point structure that stores info about the point button (it's color etc.). I have an idea, but it seems kind of overkill. I'm worried about ID ambiguity, for example that I might get into problems because a newly created buttons gets an ID (number) that maybe some static text box might already have. I'm not sure if thats bad, but I don't want that happening. I thought I'd have an array of 100 fields only for pointers to Point structures on the heap. Therefore the button ID's can start at 400 for example and go to 500, and I know this, so I can give everything else that needs an ID something outside of this range. I keep a variable that stores the number of points currently created.

If I create a point: malloc(sizeof(Point)) etc ...put a point on the heap, initialize it with the data specified by the user (e.g. color, visible etc.).
pointContainer[numOfPoints] = pointerToTheNewPoint. Then create the window...
CreateWindowEx(0, "button", "", WS_CHILD | WS_VISIBLE (or not) | BS_OWNERDRAW, x, y, width, height, hWnd, (HMENU)numOfPoints), ....);

Then when I get a WM_OWNERDRAW message, I can just use the ID to draw the appropriate button: drawButton(hDC, pointContainer[ID]....);

The only problem I see in this is that when I delete a button, I have to set all the ID's of the buttons behind the one that was deleted, so that they are in increasing order...and also decrement numOfPoints...that way I know that when I am using the ID I get sent, on the correct button in the array.
If I wanted to give a window that is already in use a new ID, would this require destroying it first and then recreating it with the new ID?

Share this post


Link to post
Share on other sites
szecs    2990
This might be a stupid idea (I tinker in C).
Have a simple array. I guess you won't have a million elements. Maximum 5000.
So I would have an item_type item[5000]; with a used flag.

Start to fill item. If you delete an item, mark it unused. If you add a new item, search for the first unused item in the array and place the new item there. Just like in a multi-bullet-shooter bullet-vector (I guess you get it).

As for ID:s, maybe the ID simply could be the position in the array.

But I'm not a real programmer. I'm just tinkering and producing ugly code.
(And I hope that helps)

Share this post


Link to post
Share on other sites
BrickInTheWall    150
Believe it or not but I had actually considered what you are suggesting since it's a method I like using when for example generating a random order of elements and need to fill all slots, and I think it's actually quite effitient. Though I'm no pro, I think this could work quite well.

Share this post


Link to post
Share on other sites
taz0010    277
Just use a vector or malloc if you're working in c only. But instead of deleting elements and having to shift to fill the gap, you swap the element you want to delete with the element at the end, then erase the element at the end. You can't use this if you care about the order of the elements, but otherwise it's extremely efficient.

Share this post


Link to post
Share on other sites
LessBread    1415
Quote:
Original post by BrickInTheWall
... still need to figure out which button it is (i.e. get it's ID)


If you have the HWND for the button you can use GetWindowLong to retrieve the id.

e.g.

DWORD id = GetWindowLong(hwnd, GWL_ID);

Quote:
Original post by BrickInTheWall
... and then associate this ID with the corresponding point structure that stores info about the point button (it's color etc.).


You can use the id as the index into a lookup table or as the index key for some other kind of searchable container - list, queue, etc.

Quote:
Original post by BrickInTheWall
I'm worried about ID ambiguity, for example that I might get into problems because a newly created buttons gets an ID (number) that maybe some static text box might already have. I'm not sure if thats bad, but I don't want that happening.


It sounds to me like you need to rethink how you've arranged the interface. Do you really need to create new buttons? Maybe you can get by with re-instancing buttons or toggling the onscreen visibility of buttons. You don't need to create a new button if you simply want to change the text displayed on the buttons. Iirc, SetWindowText should do the job.

Quote:
Original post by BrickInTheWall
If I wanted to give a window that is already in use a new ID, would this require destroying it first and then recreating it with the new ID?


No. See SetWindowLong.


Share this post


Link to post
Share on other sites
BrickInTheWall    150
I've chosen a method similar to szecs suggested. I have an array of pointers to point objects on the heap and if I delete one (using free) I just set that pointer to NULL, and when I create a new button, it goes in the first space of the array that is NULL. Also as suggested, I'm using the ID to access the array. Everything there works fine.
What I'm currently fighting with it trying to set the cursor to IDC_HAND when the user "mouseovers" on any of the buttons.
The button ID's can go from 800 to 900...here is my WM_SETCURSOR case:


int id = GetDlgCtrlID((HWND)wParam);

if (id >= 800 && id < 900)
SetCursor(LoadCursor(GetModuleHandle(NULL), IDC_HAND));
else
return DefWindowProc(hWnd, msg, wParam, lParam);

I'm not sure if calling DefWindowProc is ok here but it seemed logical to me since I was getting weird cursor behavior before.
I debugged the if (...) case gets called exactly when I want it to, but for some reason the cursor doesn't change. It flickers a bit (I think...it just looks like it but I can't see the cursor I want). Is something resetting the cursor? Or is it not being set at all? I've done a little research and have read that setting the window class cursor to NULL would help, though I'm not sure if this is true for WM_SETCURSOR cases generated by mouse overs on a windows child windows (my button controls).
Anyone have any experience with this?
Thanks for the help so far. I thought I'd ask here instead of starting a new thread.

Share this post


Link to post
Share on other sites
szecs    2990
According to MSDN the cursor is changed, but will be changed back (to the type, with which you registered the window class) as soon as you move the mouse.

To change permanently:
SetCursor(cursor);
SetClassLong(hWnd,GCL_HCURSOR, cursor);

and change back manually when you have to, of course

Share this post


Link to post
Share on other sites
szecs    2990
BTW what are you trying to achieve? It's not clear for me.
If you want to change the cursor if you move the pointer onto the widget:
Couldn't you simply register the GUI (window) class with the appropriate cursor? I think (not sure at all) that the cursor will be changed automatically to the cursor of the mouse-on-window.

And I don't get the 800-900 stuff.

And why are you doing this in WM_SETCURSOR?

Share this post


Link to post
Share on other sites
BrickInTheWall    150
As stated before the user is allowed to create little custom drawn buttons. I allow 100 of these buttons. Each time the user creates one the ID will be set to something between 800 and 900 and I can use this ID to access each button individually because I'm storing pointers to the button data in an array of 100 fields. I just want the cursor to change into a little "hand" when the user scrolls over any of the custom created buttons so he'll know that he can move it around. WM_SETCURSOR gets sent to my main window when the mouse input is not captured (MSDN). This could have a variaty of reasons, but one is also when the mouse goes over a child window. I check the ID to see if it is between 800 and 900 to see if it went over one of the user created buttons. I don't care which one of these buttons the user scrolls over, since I want the "hand cursor" on mouseover for any one of these buttons.
You might be right be right about the fact that I can register a cursor with a button, but honestly, I don't know. If I was able to do that, then that would of course be the easier alternative. I'm creating each button with the "button" class name. The only thing I'm doing differently is applying the BS_OWNERDRAW style and handling the WM_DRAWITEM message.
Is there a way to associate the cursor with a button? Otherwise I'll test what you suggested, that I use SetWindowLong to change the main cursor, and later change it back.

Share this post


Link to post
Share on other sites
szecs    2990
The widget is just a window. If nothing else, use SetWindowLong on it (them) at startup.

BTW this is just adding one line. You could have tried a lot of variations in no time...

Share this post


Link to post
Share on other sites
BrickInTheWall    150
Okay, I've got it working now using SetClassLong...but there is something I don't understand.

I use SetClassLong right after I create a button and I set it to use the hand cursor. Then in the WM_SETCURSOR message I use SetCursor...
At first it didn't work when I used this:
SetCursor(LoadCursor(GetModuleHandle(NULL), IDC_HAND));

Then I replaced GetModuleHandle(NULL) simply with NULL, and everything works.

In this example on MSDN: http://msdn.microsoft.com/en-us/library/ms648380%28VS.85%29.aspx

In "Creating a Cursor" at the beginning he creates to cursor handles, once with hInstance set to NULL and once to GetModuleHandle(NULL). I guess my question could be a bit more general. When do I use GetModuleHandle(NULL) and when do I simply pass NULL to an HINSTANCE parameter?

Share this post


Link to post
Share on other sites
szecs    2990
Why do you have to handle WM_SETCURSOR at all?

Did you try not to handle it, and see if it works automatically?

BTW I use this:
win_state.cursor.Arrow 		= LoadCursor(NULL, IDC_ARROW); 
win_state.cursor.IBeam = LoadCursor(NULL, IDC_IBEAM);
win_state.cursor.IBeam = LoadCursor(NULL, IDC_IBEAM);

win_state.cursor.sizeNWSE = LoadCursor(NULL, IDC_SIZENWSE);
win_state.cursor.sizeNESW = LoadCursor(NULL, IDC_SIZENESW);
win_state.cursor.sizeWE = LoadCursor(NULL, IDC_SIZEWE);
win_state.cursor.sizeNS = LoadCursor(NULL, IDC_SIZENS);
right before any other window init/create functions. So you don't have to load again and again.

But try not to handle it please! (I'm curious if it works, but I can't try it ATM)

Share this post


Link to post
Share on other sites
BrickInTheWall    150
Ah yeh good idea loading it only once. And yea I tried not handling WM_SETCURSOR, it doesn't work then. But that's not all.
The kicker? If I press and hold on the button and move my mouse outside of the button, there I still have the hand symbol until I let go. Should this be happening?

EDIT: It only works when NOT handling WM_SETCURSOR if I change the cursor of my button class like this: SetClassLong(hButton, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_HAND));

If I use this however: SetClassLong(hButton, GCL_HCURSOR, (LONG)LoadCursor(GetModuleHandle(NULL), IDC_HAND));
it doesn't work...can anyone explain to me why not? I'm not really confused as I don't really know anything about the HINSTANCE type, but if anyone could direct me to a source that would clarify things, I'd be happy.

Share this post


Link to post
Share on other sites
szecs    2990
Sorry I don't get it.
Does it work or not?

BTW NULL means desktop. So (I'm not sure at all, and I'm not good with terms) you load the default cursors and stuff. If you pass a non zero valid instance handler, then I have no idea. I guess a regular window simply doesn't have those resources.

BTW I have no idea what you want with the GetModuleHandle.
What does MSDN tell you about that?
And if you are really curious, start a new thread instead.

But anyway, does it work the way I suggested or not?

Share this post


Link to post
Share on other sites
BrickInTheWall    150
Yes, I was confused as to why it wasen't working before, since your method seemed legit. Yet I don't get why NULL instead of GetModuleHandle(NULL) works. Oh well, maybe someone here can tell me.

Thanks for the help!

Share this post


Link to post
Share on other sites
szecs    2990
I think I get it:

GetModuleHandle returns handle to your .exe, that means you can load your custom resources embedded in the .exe (your buttons/widgets/custom-cursors/whatever)

But to load window resources (like the built in cursors), you must pass NULL, that means Windows' own built in resources.

My terms may be imprecise, but I guess I'm right anyway.

So if you have a custom cursor (drawn by you), use GetModuleHandle, but if you want a built in cursor, use NULL.

(shit, I'm starting to get win32...)


GetModuleHandle

Share this post


Link to post
Share on other sites

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

Sign in to follow this