Windows / GUI Functionality in Blitz+
Blitz3D Forums/Blitz3D Beginners Area/Windows / GUI Functionality in Blitz+
| ||
Hi, I'm pretty new to Blitz+, but I've started work on a Windows-based Map Editor and I'm looking for some resources on the Windows-based GUI functions. The documentation that comes with Blitz+ is all very well, but there are precious few examples of how this all fits together, and some of the functionality (Create a window, update it, create gadgets, assign to group, release gadget, etc., etc.,) can be somewhat daunting. I *refuse* to use a GUI editor - it makes more sense (to me, anyway!) to learn how these things are done :) My question is - does anyone know of any decent tutorials or articles which cover the GUI aspect of Blitz+? Cheers, Andy/Thalamus |
| ||
I don't, but it's fairly straightforward after looking at some examples. I would start out with a nice event-based barebones:wndMain=CreateWindow("",200,200,200,200) Repeat Select WaitEvent() Case $803 : End End Select Forever You can put in all the events you want to watch for in the Select WaitEvent() structure, including GadgetAction events (for when you have multiple gadgets, you can put a Select EventSource() in that event's case to figure out what gadget was used) and Timer events if you want to do something every 60th of a second, for example, render a game. Just say CreateTimer(60) upon initialization. As for what you're asking for... Create a window: Use the CreateWindow command, that's really all there is to it. Update a window: All the normal window operations update normally. You can use other Window commands AND some gadget commands on windows to hide them, disable them, resize them programmatically, etc. Create Gadgets: It's as simple as using any of the "Create..." functions listed under their respective gadgets (in the docs). Just set the parent to be the window handle (wndMain in this case). Assign to group: When you create the gadgets, instead of assigning the parent to be wndMain, first create a panel (CreatePanel) whose parent is wndMain, then create the gadgets that you want to be grouped and assign their parent to be the panel. Release gadgets: It's as simple as calling Freegadget, which will remove the gadget and any of its children. It works with windows too. Take a look at the samples, and ask any questions you might have. |
| ||
Hey, you could try: http://www.blitzcoder.com/ They have alot of tutorials and guides :o) |
| ||
So a gadget is basically *anything* the user interacts with? And you have to assign a gadget to a particular window (hence, group)? I'm currently ploughing through the samples, dissecting them piece by piece, but it is hard going sometimes. WHat it really needs is a nice big tutorial taking people through every simple step... maybe I'll wind up writing it :) Thanks for the help though, chaps - expect to see a lot more questions posted over the weekend ;) |
| ||
So a gadget is basically *anything* the user interacts with? In BlitzPlus, pretty much, except for maybe menus. And you have to assign a gadget to a particular window (hence, group)? Yes, all gadgets have to have a particular group (parent) -- even windows. (Their group is "0" -- the desktop.) |
| ||
Cheers, soja. Any other useful rules of thumb I should bear in mind? :) |
| ||
Not off the top of my head, but I'd be glad to pipe in if you have any more questions or run into problems. |
| ||
Ok, I seem to be getting into the swing of things a little more. Couple of questions, though: 1. It doesn't seem possible to add spacer lines to a toolbar. The only way around this I can think of is to add a spacer icon to the toolbar graphic but disable it in the the program so it can't be clicked on. 2. Is there any way to add keyboard shortcuts to menus? What about nested menus, too? 3. Is there any way to force the size of a window when it is resized? E.g. the window can only be multiples of 32. |
| ||
1) You're probably right, but I've never done a toolbar. 2) Prefix the "menu item &letter" with an ampersand (&), and the following letter will be underlined. That's not really a blitz-specific thing, just a general programming language/Windows thing that I know from Visual Studio. 3) The only thing I can think of is to watch for event $802 (window size) in your event loop, and force it not to change unless the mouse moves (mousex(), mousey()) changes > 32 pixels? |
| ||
It is only possible to add spacers via some WinApi calls. Check the code archives ( http://www.blitzbasic.com/codearcs/codearcs.php?code=704 ) You can add keyboard shortcuts. Perhaps you should read the docs ;) Shortcut keys can be implemented using the HotKeyEvent function. If you wish to show the keyboard shortcut in the menu, you should separate it from the main text using Chr$(8) or Chr$(9). This will ensure that it is correctly positioned in the menu. Chr$(8) results in right aligned shortcuts, whereas Chr$(9) results in left aligned shortcuts. See the example for details. ALT shortcut keys can be implemented by preceding the desired character in text$ with an ampersand (&). For example, if text$ is set to "&Copy", then the keyboard shortcut ALT-C will trigger the menu event. To force the size you would simply put a test for event $802 in your main loop, and then resize your window gadget according to your speciffic requirement. $802 - Window size Generated when the user sizes a window. EventSource contains the handle of the window gadget that has sized, and EventX and EventY contain the new size of the window. |
| ||
>> Perhaps you should read the docs ;) I did - I just negelected to check out the online docs as opposed to the ones built into the program :) Cheers for the pointers - things are working beautifully :) |
| ||
Is there a way to check when the mouse *isn't* over the canvas? |
| ||
You could keep track of events $205 and $206 (Mouse Enter, Mouse Leave) You could also get the absolute position of the cursor in screen coords (MouseX(), MouseY()) and figure out if those coords fall within the coundaries of the canvas. |
| ||
>> You could keep track of events $205 and $206 (Mouse Enter, Mouse Leave) So how would I use this to check if the mouse was over a particular canvas? |
| ||
If one of the events fire, use Select on EventSource(), and it will tell you which canvas it happened to. You will have to keep a variable (canvas1=createcanvas...) of the canvas handle when you create it to check, though. The only case where I can see where this wouldn't work is when the program first starts, and the mouse pointer starts inside the canvas right away. Well, you can always use MouseX and MouseY in that case, or just check the MouseMove event. |
| ||
>> If one of the events fire, use Select on EventSource(), and it will tell you which canvas it happened to. I'm still wrestling with this... grrr... |
| ||
I made one rather big app once .. that was my first B+ thing that had more than 20 lines of code .. after that I knew almost all ins & outs of B+ .. Just make something useful, not 'big' by concept, but some small tool.. it'll turn out to grow big anyway :) That first 'big' app of mine was an image-converter that loaded bmp images with 16 colors, and the app included a palette-manager in which you could move around and modify some colors (with some simple RGB thing). Such a simple concept became many hundreds of lines.. Map-editors er even simpler to make if you already know some 2d commands & stuff.. |
| ||
Well I've coded the Commodore 64 in 6501, and the PC in 80x86 assembly, so I'm not alien to programming in general. I can't really complain about the learning curve for Blitz+, it's just that the docs and tutorials could be a little weightier. I'm certain I'll end up writing a GUI tutorial... ;) |
| ||
The tutorials could be a bit more uniform.. they're a bit of a mixed-bag in terms of style. Anyway, all the gadget-stuff more or less works the same.. if you know one, you know all, and you'll learn it in no-time. In a week you're laughing about this post :) |
| ||
I'll be laughing when I get this "which canvas is the mouse over" problem sorted... |
| ||
like this? (run in debugmode!)app=CreateWindow("blah",0,0,640,480) Dim c(15) For t=0 To 15 c(t)=CreateCanvas(16+t*32,0,32,80,app) SetBuffer CanvasBuffer(c(t)) ClsColor Rnd(0,255),Rnd(0,255),Rnd(0,255) Cls FlipCanvas c(t) Next SetBuffer DesktopBuffer() quit=False ;------------------------------------------------------- Repeat WaitEvent() If EventID()=$803 quit=True For t=0 To 15 If EventSource()=c(t) DebugLog "canvas c("+t+")" EndIf Next Until quit ;------------------------------------------------------- ; optional cleanup duties here.. End |
| ||
Ok, your code works, mine doesn't, and so I have been struggling to work out why my checks for events $205 and $206 didn't work. Here's my code as-was (apologies for poor layout): ;---------- Repeat e = WaitEvent (fps) Select e Case EVENT_Close : End Case EVENT_Menu Select EventData() Case 2 : loadmap2() Case 3 : savemap() End Select End Select Until e = EVENT_Timer ;---------- Now, that seems to work fine - the menu and close commands work fine by checking (e). But if I add the following: ;---------- Case $205 If EventSource() = canvas canvasactive=1 EndIf ;---------- ...it point blank refuses to work. :/ The only way I can get it to work is to add: ;---------- If EventSource()=canvas canvasactive=1 EndIf ;---------- I appreciate that there are a few things that I might be misunderstanding - the code loop was copied from another program and I *thought* I understood it correctly. What I don't understand is: a) why it fails to recognise the "Case $205" b) why I can do a select on "WaitEvent" and don't have to use "EventID" Help :( |
| ||
a) I'm not sure without seeing the whole of your code, but you've got to make sure you've put it in the right place (e.g. after "select e"). I'm also not sure when it drops out (what's EVENT_Timer?). It would help to see the whole loop and pertinent information in [ code ] [ / code ] brackets. b) When you select on WaitEvent, you *do* select on the EventID, because WaitEvent returns an event ID. |
| ||
a) EVENT_Timer = $4001 Code as follows: ;---------------------------------------- ; Frame timing stuff... limit=CreateTimer(60) ; FPS rate ;---------------------------------------- ; Main loop... Repeat SetBuffer CanvasBuffer (canvas2) Cls SetBuffer CanvasBuffer (canvas) Cls update_map(0) update_grid() update_tilestrip() control() update_cursor() text_display () ;---------------------------------------- Repeat e = WaitEvent() If EventSource()=canvas canvasactive=1 canvasactive2=0 EndIf If EventSource()=canvas2 canvasactive=0 canvasactive2=1 EndIf Select e Case EVENT_Close : End Case EVENT_Menu Select EventData() Case 2 : loadmap2() Case 3 : savemap() End Select End Select Until e = EVENT_Timer VWait:FlipCanvas canvas:FlipCanvas canvas2 Until (KeyHit (1)) ;---------------------------------------- |
| ||
I'd say that the code could be organised somewhat better .. Anyway.. try to study this map-editor example below.. I think I broke my record in fast-map-editor-coding :) It doesn't save the maps, but that's easy to implement.. ; miniminiminiminiminiminiminiminiminiminiminiminiminiminimap-editor - by CS^TBL app=CreateWindow("blah",0,0,640,480) Global c1=CreateCanvas(8,8,32,256,app) Global c2=CreateCanvas(56,8,512,256,app) Global img,currentpalette=0,maplmd=False Dim map(32,16) loadimg=CreateButton("IMG",8,8+256+4,32,24,app) UpdatePalette(True) quit=False ;------------------------------------------------------------------------ Repeat WaitEvent() If EventID()=$803 quit=True ; window-close / alt-f4 If EventID()=$401 ; generic gadget action.. like a buttonpress If EventSource()=loadimg ; request a file whichfile$=RequestFile$("load image..","jpg") If whichfile$<>"" ; did we succeed? If img FreeImage img ; first get rid of the old image img=LoadImage(whichfile$) UpdatePalette(True) EndIf EndIf EndIf If EventSource()=c1 ; the palette? ------------------------------------------------ If EventID()=$203 ; mousemove x=EventX()/16 y=EventY()/16 UpdatePalette(False) Color 255,255,255:Rect x*16,y*16,16,16,False Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False FlipCanvas c1 EndIf If EventID()=$206 ; mouseleave UpdatePalette(True) EndIf If EventID()=$201 ; mouseclick x=EventX()/16 y=EventY()/16 currentpalette=(x Mod 2)+y*2 UpdatePalette(True) EndIf EndIf If EventSource()=c2 ; the map? ------------------------------------------------ If EventID()=$203 ; mousemove x=EventX()/16 y=EventY()/16 UpdateMap(False) Color 255,255,255:Rect x*16,y*16,16,16,False Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False FlipCanvas c2 EndIf If EventID()=$206 ; mouseleave UpdateMap(True) EndIf If EventID()=$201 ; mouseclick maplmd=True EndIf If EventID()=$202 ; mouseclick maplmd=False EndIf If maplmd x=EventX()/16 y=EventY()/16 If x<0 x=0 ; make sure you clip! otherwise you'll get errors! If y<0 y=0 If x>31 x=31 If y>15 y=15 map(x,y)=currentpalette UpdateMap(False) Color 255,255,255:Rect x*16,y*16,16,16,False Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False FlipCanvas c2 EndIf EndIf Until quit FreeImage img End ;------------------------------------------------------------------------ Function UpdatePalette(swap) SetBuffer CanvasBuffer(c1) Cls If img DrawBlock img,0,0 x=currentpalette Mod 2 y=currentpalette /2 Color 255,255,255:Rect x*16,y*16,16,16,False Color 0,0,0:Rect 1+x*16,1+y*16,14,14,False If swap FlipCanvas c1 End Function ;------------------------------------------------------------------------ Function UpdateMap(swap) SetBuffer CanvasBuffer(c2) Cls For y=0 To 15 For x=0 To 31 tile=map(x,y) px=tile Mod 2 py=tile / 2 If img DrawBlockRect img,x*16,y*16,px*16,py*16,16,16 Next Next If swap FlipCanvas c2 End Function |
| ||
>> I'd say that the code could be organised somewhat better .. Definitely. The code as it stands is a legacy of bits and pieces I've picked up from various places (the map editor began as a hybrid of two existing editors). If I'd had a decent tutorial or a chunk of example code such as yours at the beginning, it wouldn't look as shabby as it does :) Restructuring my main loop is something I've been meaning to do for a while, though - there's a couple of things I'd do slightly differently (though I *really* like your maplmd/maprmd system), but yours is a nice, crisp, well-presented example. Cheers :) >> Anyway.. try to study this map-editor example below.. So basically, my code wasn't working because I should have been checking the EventSource and *then* the EventID...? Blimey! |
| ||
Remember that EventSource(), EventID() etc. is nothing magical at all.. they're just variables containing an int-value (4 bytes)! So If A If B Endif Endif means the same as: If EventSource()=c2 If EventID()=$203 Endif Endif So, if a certain programflow suits you better, then go for it.. If you need to do a lot of ID-type checks on the same gadget then it's only logical that you put the EventSource() at the top of the tree (like I did when reading out the canvas-related actions). However, if you have 20 buttons (where only ID $401 is useful) on the screen then you usually want to check them the other way around. If EventID()=$401 ; buttonpress/gadgetaction If EventSource()=LoadIMG Endif If EventSource()=SaveIMG Endif If EventSource()=LoadMAP Endif If EventSource()=SaveMAP Endif Endif However.. you get events when there are events.. EventID() is only to specify which event. If you've a button, then there's only the $401 event. You don't need to specify $401 then as you know it'll always be $401 anyway. So, compare the last above example with this one: app=CreateWindow("",0,0,170,170) b=CreateButton("s",5,5,30,30,app) Repeat WaitEvent() If EventID()=$803 quit=True If EventSource()=b quit=True EndIf Until quit End If you click the button, the app ends.. without "$401" anywhere in the source :) |