Well, the general idea is not to let UI handle all exceptions, nor that makes much sense. Lets say you have a data layer implemented with ADO.NET. The general pattern here is to handle SqlExceptions in data layer, and then wrap the SqlException in a more meaningfull DatabaseLayerException that upper layers should handle - and you follow this pattern all the way to the top, so you can have something like InfrastructureException, ApplicationException etc...
On the very top, you catch all ApplicationExceptions that went unhandled (and you make all your exceptions inherit this one for polymorphism), and you catch all unhandled Exceptions as a special case not likely to happen, and try to recover from it.
I also suggest use of logging, either manually or with AOP - you will find plenty of resources online (perhaps Log4Net ?).