Introduction
There are plenty of questions answered on StackOverflow, and perhaps even some on CodeProject that may touch on this subject, but I thought it would be worthwhile to post.
I wanted a way to load in controls without having to type out a new one each time. The main hope (eventually) for this method is that I can extract out the commands into a separate DLL. That way when I go to update the program, I only have to update the command DLL and not send a whole new version. This helps to limit the size of my update file, and can also aid in customization of the program for individual customers.
Background
This article assumes that you have basic knowledge of WPF or winforms controls. The code is quite simple (< 100 lines) but you will need at least a familiarity with WPF to read it.
Using the code
The example code provided includes several classes, all of which are kept small for this example. Let me run through them quick:
- MainWindow:
The mainwindow is standard in any WPF GUI project, and is used here to be the window we dynamically load our commands into. It also houses the reflection logic that actually loads the controls in.
- ICommand:
This is an interface class for all of the commands. This must be used so that reflection can find your command classes. If there is no interface class, there is nothing linking your separate command classes.
- Commands:
This class is used for an enum only. This is not a necessary class, but you may find it useful for a more complicated implementation. Right now I keep a copy of the actual command on the
MyButton
class (see "MyButton" description). The reason I wouldn't do this in a more complicated program is that it is better to use a "SetCommand
" method that will set the command based on what the enum is, rather than using an actual Command object. This prevents any multiple instances/leftover classes lingering around. Also, if you have other implementations of that class (another constructor, for instance), you would want to create that one instead of the default.
- OpenCommand/SaveCommand:
These are the basic commands being used. Notice that I only have "Name" and "Command" on them, along with the execute method. In more complicated implementations of this you may want to include tooltip, toolbar, toolbar location, etc. You may find that you want to put all this information in another class if you store too much as well.
Important: You must create a standard constructor for the below implementation. The
CreateInstance
the way I'm using it does not recognize parameters. You can achieve this, however, simply by doing
CreateInstance(type, object[] parameters)
. It will find the constructor
(if it exists) that best fits the parameter type.
- MyButton:
Because we need to store either the enum or a reference to the command, we must extend the Button into our own. In a more complicated implementation of this, I've actually created a user control that houses a button, and extended it that way
for a little more flexibility.
Let's look at the main code used for reflection:
private void DynamicallyLoadCommands()
{
Type type = typeof(ICommand);
var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p));
ToolBar bar = new ToolBar();
foreach (Type classType in types)
{
if (classType.Name == "ICommand") { continue; }
ICommand command = (ICommand)Activator.CreateInstance(classType);
MyButton button = new MyButton();
button.Content = command.Name;
button.command = command;
button.Click += new RoutedEventHandler(button_Click);
bar.Items.Add(button);
}
CommandPanel.Children.Add(bar);
}
The first thing we do is acknowledge that we're going to be getting files of type
ICommand
(our implementation, not the System.Windows.Input
one. Be careful!)
Next we use some LINQ to select our assembly types where type.IsAssignableFrom(p)
. This finds any classes that are linked to our
ICommanddd
interface.
Skipping over the toolbar creation, we enter a loop that cycles through any classes of that type, ignoring
ICommand
itself, and creates an instance of it.
From that instance we are able to get the Name, and the command itself and assign it to
MyButton
.
The last thing we do is add our button to our Toolbar, and add the Toolbar to a "CommandPanel
", which is just a
StackPanel
in WPF.
Points of Interest
As mentioned, using Activator.CreateInstance(classType)
will use the default constructor.
If you have constructors in your commands that you need parameters for, you can use Activator.CreateInstance(classType, parameters)
.
Keep in mind though that this is cycling through all of your commands, so you will have to handle any special cases with different parameters.