It looks like you've fallen into some of the common pitfalls, but don't worry, they can be fixed :)
First you need to look at your application a little differently and start breaking it down into chunks. We can split the chunks in two directions. First we can separate controlling logic (The business rules, data access code, user rights code,all that sort of stuff) from the UI code. Second we can break the UI code down into chunks.
So we'll do the latter part first, breaking the UI down into chunks. The easiest way to do this is to have a single host form on which you compose your UI with usercontrols. Each user control will be in charge of a region of the form. So imagine your application had a list of users, and when you click on a user a text box below it is filled with their details. You could have one user control managing the display of the user list and a second one managing the display of the user's details.
The real trick here is how you manage the communication between the controls. You don't want 30 user controls on the form all randomly holding references to each other and calling methods on them.
So you create an interface for each control. The interface contains the operations the control will accept and any events it raises. When you think about this app, you don't care if the list box list selection changes, you are interested in the fact a new user has changed.
So using our example app, the first interface for the control hosting the listbox of users would include an event called UserChanged which passes a user object out.
This is great because now if you get bored of the listbox and want a 3d zoomy magic eye control, you just code it to the same interface and plug it in :)
Ok, so part two, separating the UI logic from the domain logic. Well, this is a well worn path and I'd recommend you look at MVP pattern here. It's really simple.
Each control is now called a View (V in MVP) and we've already covered most of what is needed above. In this case, the control and an interface for it.
All we're adding is the model and the presenter.
The model contains the logic that manages your application state. You know the stuff, it would go to the database to get the users, write to the database when you add a user, and so on. The idea is you can test all of this in complete isolation from everything else.
The Presenter is a bit more tricky to explain. It is a class which sits between the model and the View. It is created by the view and the view passes itself into the presenter using the interface we discussed earlier.
The presenter doesn't have to have its own interface, but I like to create one anyway. Makes what you want the presenter to do explicit.
So the presenter would expose methods like ListOfAllUsers which the View would use to get its list of users, alternatively, you could put an AddUser method the View and call that from the presenter. I prefer the latter. That way the presenter can add a user to the listbox when ever it wants.
The Presenter would also have properties like CanEditUser, which will return true if the user selected can be edited. The View will then query that every time it needs to know. You might want editable ones in black and read only ones in Gray. Technically that's a decision for the View as it is UI focused, whether the user is editable in the first place is for the Presenter. The presenter knows because it talks to the Model.
So in summary, use MVP. Microsoft provide something called SCSF (Smart Client Software Factory) which uses MVP in the way I've described. It does a lot of other things too. It's quite complex and I don't like the way they do everything, but it may help.
I don't consider "an application should be fully explained by its own code" a fundamental programming principle. There are lots and lots of things which are not explained by just looking at the code of an application. Apart from knowing the basic things of the programming language itself (syntax and semantics), you need to know the conventions. If an identifier in Java starts with a capital letter, it is a type. There are lots of these conventions you need to know.
Convention over configuration is about reducing the amount of decisions the programmer has to make about things. For some things this is obvious -- nobody would consider having a language where the capitalization of types is something you need to declare at the top of your program -- but for other things it is not so obvious.
Balancing convention and configuration is a difficult task. Too much convention can make code confusing (take Perl's implicit variables, for example). Too much freedom on the programmer's side can make systems difficult to understand, since the knowledge gained from one system is rarely useful when studying another.
A good example of where convention aids the programmer is when writing Eclipse plugins. When looking at a plugin I've never seen, I immediately know many things about it. The list of dependencies is in MANIFEST.MF, the extension points are in plugin.xml, the source code is under "src", and so on. If these things were up to the programmer to define, every single Eclipse plugin would be different, and code navigation would be much more difficult.
Best Answer
The way I've done this in the past (on other systems) is using a repository. The common files are all in a library part of the repo, but each solution maps them to an individual project.
That way, all solutions all use the same source code, but each solution only uses the files it needs.
This MS blog post tells you how to do it (for VS2005).
Note that if you do this, you need to make sure that all common functionality is completely covered by unit tests. Otherwise, changing the functionality of one of these common files in a way that only one of the including solutions needs could break multiple solutions.