New-ish REALbasic features (Part Seven)

| | Comments (5)

Last time we saw how to make the dialog useful by providing a dialog box callback. This time we're going to make a simple change to make the dialog less ugly. Right now it's using the "System" font, which is rather hideous. This is not to be confused with the RB "System" font, which is actually a metafont. Instead, it's using the honest-to-goodness ancient Windows 1.0 system font. Let's make it use the MS Shell Dlg font instead so that it looks a lot more like a typical dialog.

To do this, we want to specify the point size and typeface name directly after the dialog's title. The dialog box manager will use this font when creating the dialog and all of its controls.

For starters, we no longer want to add one to the end of the call to MultiByteToWideChar call. We had that in there because we wanted to skip over the font. But now we don't want to skip those bytes, but instead specify them. After removing the plus one, we want to specify a 16-bit point size, and the typeface name. Your code will look like this:

dim numChars as Integer = MultiByteToWideChar( CP_ACP, 0, "My Dialog!", -1, dlgTemplatePtr, 50 )
dlgTemplatePtr = Ptr( Int32( dlgTemplatePtr ) + (numChars * 2) )
// Now we're going to specify the font that we want the dialog to use.  This happens on the WORD
// boundary after the dialog's title.  It's a point size and typeface name
dlgTemplatePtr.Int16 = 8
dlgTemplatePtr = Ptr( Int32( dlgTemplatePtr ) + 2 )
numChars = MultiByteToWideChar( CP_ACP, 0, "MS Shell Dlg", -1, dlgTemplatePtr, 50 )
dlgTemplatePtr = Ptr( Int32( dlgTemplatePtr ) + (numChars * 2) )

That's the only changes we need to make! If you run the project now, you'll see the dialog uses a better font and a small point size.

You may be wondering where to go from here. It seems like you know just about everything there is to know about making a dialog template in memory. Well, if you want to take this project to its final step -- generalize the functionality into a series of APIs to make it easier to generate a dialog on the fly. I'm going to leave this up to the reader, but I think a good discussion of what the API should be is a reasonable way to conclude this series.

The basic idea is that you have an allocation function which is responsible for creating the initial block of memory, and a series of APIs which then fill out the memory. Finally, one function at the end is responsible for displaying the dialog and cleaning everything up.

Function CreateDialog( x as Integer, y as Integer, width as Integer, height as Integer, title as String, style as Integer, extendedStyle as Integer ) as Integer
Function AddControl( dlgHandle as Integer, type as Integer, x as Integer, y as Integer, width as Integer, height as Integer, title as String, style as Integer, extendedStyle as Integer, id as Integer ) as Integer
Function DisplayDialog( dlgHandle as Integer, ownerWindow as Window ) as Integer

There are a few things you need to watch out for. Thing one is that we are going to be passing the pointer around, but we still need to be able to get the pointer's original value so that we can pass it to the call to DialogBoxIndirect. The easiest solution to this is to reserve the first four bytes of the pointer as being the current write location. So the dialog box template starts at Ptr value + 4 when you go to pass it in to the dialog box. Before you leave a method which has written to the pointer, you simply need to update the value at the start of the pointer. So your code would look something like this:

dim actualPtr as Ptr = Ptr( dlgHandle ).Ptr
// Work with actualPtr, advance it however you'd like
Ptr( dlgHandle ).Ptr = actualPtr

When you want to call DialogBoxIndirect, you just need to skip over our reserved memory, like this:

ret = DialogBoxIndirectParamW( moduleHandle, Ptr( dlgHandle + 4 ), ownerWindow.Handle, AddressOf DialogProc, 0 )

Remember, this trick can be used to store whatever information you'd like. Just reserve whatever space you need.

The second thing you need to watch out for has to do with memory consumption. We allocated 1024 bytes to work with, but when using the general API set, you need to be prepared to handle the case where you've run out of memory. This is where the GlobalReAlloc API will come in handy. Just remember that you need to keep track of the memory and be able to handle the situation when you're running out of it.

I think this just about covers everything I had initially set out to cover. If you have questions or comments, don't be afraid to speak up! I'm curious to know if anyone even made it this far. ;-) But seriously, is this something that you find interesting?

5 Comments

it is interesting but damn this new blog makes the code hard to read

Replies to topics really seem to have plumetted since you moved to the new servers or maybe that's just an odd coiincidence ?

Replies went down when I stopped posting regularly, and only posted personal posts. That was before the switch to the new format.

As for code being hard to read, I'm still waiting on some benevolent benefactor to swoop in and help me get something working... I've got the debugger to work on and don't really have time to devote to figuring out MT's strange reluctance at displaying the information I enter into a post.

It seems like these days there's almost nothing you can't do via RB if you are willing to look up the APIs.

It seems like more and more, almost all of the runtime framework could (potentially) be written in RB itself. I'm sure a good bit already has been. The mere possibility of that is a big milestone for a development environment, and if that actually begins to happen- that could potentially lead to even more intelligent runtime size reduction in executables, among other things.

Thanks to your efforts, I've been able to demonstrate that RB is not a "child's toy" to several people. In fact my list of things I have to leave the RB world for has shrunk quite a bit over the past two years. Now all I have left:

-Lower impact dictionaries/object overhead (dictionaries in RB take up far more memory compared to the generic version in C#- because in RB they work specifically with Variants and all the object overhead that entails) This isn't something most people run into if they code properly, but it can happen for large projects.

-Native threads that can spawn to multiple CPUs. C# makes this easy (I can spawn two instances of a given method out to two CPUs without much thought). Obviously to do this you have to define what is thread safe and what isn't.

-Image handling via console/service apps. I've actually had to do this a couple of times lately- I wouldn't have thought it would be something I'd run into but I have.

-Optimizing compiler. This one isn't too much of an issue for me, since I can usually profile and hand optimize to the solve the most pressing issues. But it'd save a lot of time and code ugliness from loop unrolling/etc if the compiler just handled it. :)

Speaking of the above, anyone know of a profiling tool for Windows that works like Shark does for OS X on RB apps? Right now I just insert my own timing tracking, a utility that does this for me using name symbols would be great...

I wasn't watching quite that closely but it seemed that the replies had tailed off a bit, then the move has really crished them.

Not sure if you get viewer stats as I'd suspect there are probably as many reading, just no posts.

Yeah, I don't have my stats like I used to. But you're right, I think the holiday season, lack of posts, server switch all helped to kill off comments for a while. It'll pick back up though.

Now, if only I could find some bright person to help with website formatting. :-)

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.