Dual UIs: Windows Forms and WPF in the Same Application
A little bit ago I mentioned that I was working on v2 of Walnut and adding a WPF user interface. As I was thinking about the new UI I thought that it would be convenient to keep the “old” Windows Forms UI around, so that I wouldn’t have to require people to have .Net 3.0 installed to use Walnut. This is kind of similar to an Ajax web application gracefully downgrading when users have javascript disabled.
After thinking about how to implement these dual UIs, I came up with what I think is a pretty cool solution. In v1 of Walnut, there are two projects in my soluion file:
- Walnut.Core - This is a class library that contains the business logic of Walnut as well as other application code that doesn’t relate to the UI.
- Walnut.Shell - This is the Walnut.exe executable that contains the Windows Forms UI and references the Walnut.Core project.
When I press F5 in Visual Studio, the project that gets started is Walnut.Shell.
Now in v2 I have three projects:
- Walnut.Shell - This is now class library project that contains the Windows Forms UI implementation.
- Walnut.WPFShell - This is the new project that is also a class library and contains the WPF UI implementation.
- Walnut.Core - This project is now the Walnut.exe project and contains the same business logic.
So now instead of starting the Walnut.Shell project, the Walnut.Core project contains the Main() method that starts Walnut. When the application starts up, it determines which shell to start(Windows forms vs. WPF) and loads the correct assembly. To facilitate this, I added an interface to Walnut.Core called IShell that is defined as follows:
public interface IShell
{
void RunUI();
void Initialize();
void Shutdown();
}
Each of the shell projects has a class that implements this interface. When the application starts and determines which shell to load (which is a config setting for now), it loads the assembly and finds the class that implements the interface, using reflection. Once it finds the class I just have to instantiate it and call the RunUI() method which starts the UI.
After I got this concept working in the code, I had to make some changes to the way the projects build in Visual Studio. I have an MSBuild script which makes a release build so that I can package it up into an installer, but I also like to be able to press F5 in Visual Studio to get some instant feedback on code. Since the Walnut.Core project doesn’t reference either of the two shell projects (that would make a circular reference) I had to set the build directory for each project to the same location, otherwise the assemblies wouldn’t exist in the right locations.
I also had to change the MSBuild script as well as the Wix script, no big deal. Now all I have to do is learn WPF
Jake Good said,
Wrote on February 28, 2007 @ 1:12 pm
I like the idea! In fact… I’ve been meaning to do something very similar in my toy projects…
jake
Unable to load one or more of the requested types « Ben Reichelt’s Weblog said,
Wrote on March 19, 2007 @ 5:35 pm
[…] After I ran the build, I was getting this error: “Unable to load one or more of the requested types”, this is related to the reflection I use to dynamically load either the WinForms shell assembly, or the WPF shell assembly. It was crapping out when trying to iterate over the types in the WPF assembly. […]