The toolkits are really just a starting point.

Most of them work on a callback manner, similar to how you'd deal with device drivers, e.g. someone pressed a button and you'd respond with showing/hiding another panel. However, if you wrote code like that, it'd be completely spaghetti. So you are asking a very valid question here.

The second issue is most GUI tookits will only allow you to interact with the toolkits on the UI thread. Each language/tookit have their own style of queuing up some functions to be run on the UI thread. You need to figure out what is the canonical way of dealing with this up front.

The third issue is most callbacks occur on the event thread. This means that you cannot run long processes as the UI literally freezes until you hand control back to the main event loop.

Testing is another source of pain. Most GUI toolkits were not built with testing foremost in mind. However, it is not necessarily easy to shim/mock the GUI components because they can hold quite a bit of state.

Now that we got that out of the way, I'd like to say that if you have developed headless utilities, then you are actually in a better situation than people who started programming by learning GUI.

You could start off by deconstructing existing applications.

First off, enumerate all the states a UI could be in e.g: New, Dirty, Validating, Saving, Saved. Note how these states are shown on the UI. There might be labels for error messages that are normally hidden, and made visible when validation errors occurs, etc. I like to have it all centralized in one subroutine, so that when the state changes, the routine makes sure all the UI elements are in the right state. These states also govern whether you need to prompt users to "Discard, Save or Cancel" when they close the application.

Secondly, if this is your first project, stay away from data binding. I usually have a model which essentially duplicates all the UI information, and have a pair of routines pushToUi(), pullFromUi() to marshal back/forth the visual state. While this is somewhat repetitive, it allows you to make minor changes your UI - (e.g. DatePicker instead of a TextBox, move a field to a panel that is usually hidden) and your program will still work.

Thirdly, a GUI app usually has three cooperating components - Model (your data), View (the toolkit) and Controller (the glue). Limit the interaction and your application will be more easily testable. For instance, you could have the Model pushing data to the View, the View pushing events to the controller, and the controller changing state on the Model. There are probably 6 ways you can combine the interaction, but try to avoid two-way interaction between all three components - it will be difficult to figure out.

Fourthly, asynchrony is a fact of life. Don't perform database calls (and definitely not a call to a web service) on the main UI thread. Put the job on a queue, and let the background thread do it. Microsoft .NET have a BackgroundWorker and thread pools to tackle this. You can use some of their ideas if your platform doesn't have this. An application that is full of callbacks can be difficult to grok. This brings me round to my original point that if you've written device drivers before, you already know this, and probably have some techniques to tackle it in a sane manner. If you don't - judicious use of coroutines via Duff's device, or the yield statement can do wonders for your code in terms of readability.

Fifth, be aware that event handlers attached to objects will prevent those objects from being garbage collected, even though they are no longer visible. Make sure you unwire the handlers before you lose your final reference.

As far as designing the UI goes, just bear in mind most people using the app couldn't care less about the app. All they want is to use it to get their work done and go home. So your task is to figure out which is the most common thing they want to do and unclutter the UI so they can figure out how to do it quickly. This is where your program design comes into play. A well laid out application will be easy to modify when you iterate without having to tear up all the business logic.