provides a framework that aids in the creation of WPF applications that follow the
design pattern. The goal when developing with Onyx
is to create XAML based
that have no code in the codebehind file (remember, this is a goal, not a requirement, and one should be pragmatic). In addition, the
should contain no code that ties it to any specific UI or UI features that are not easily unit tested.
Unfortunately, it's not always easy to follow the
pattern. Sometimes you need to synchronize state between the View
for which data binding doesn't work. For example, you can't bind the selected items in a multi-select
to a collection in the ViewModel
just through standard data binding. Worse, there's often a need to do something in response to user input that would make your
tightly coupled to the UI and hinder unit testing. For example, in response to the user initiating a
command you may need to display a SaveFileDialog
. If you follow any WPF discussions about
you can probably think of several dozen other examples of things that people have struggled with.
Usually, when someone asks a question about how to accomplish something that's difficult to do while using WPF and
, the experts recommend one of two approaches. The first is to be creative with the WPF framework. Often advanced techniques such as "attached behaviors" can go a long ways towards providing a solution. Though these approaches work, and
may even be the best answer in many cases, they require advanced techniques and out-of-the-box thinking. Worse, sometimes it's not obvious how you'd use these techniques to solve a specific problem, especially in a clean and reusable manner. In those
cases, the second solution proposed by the experts is to use an interface. This is a well known strategy for general decoupling of code, and is used in non-UI centric code rather extensively. This is a solution that should always work. However, it's not
without it's own set of problems, which the WPF experts generally just ignore, leaving solutions up to the inquirer.
When you want to use the interface approach to decoupling, the biggest problem that must be solved is how to supply an interface to the
. There's two very well known patterns that one can employ here:
. By default, Onyx uses
, though it's possible to modify the behavior to use
. For the rationale behind this, see
Let's take a high level view of how Onyx works. Note that we'll be working backwards from how you'd likely develop using Onyx if you're following a TDD approach, but this is probably the best way to understand how to use Onyx.
The first thing to look at is how you associate a ViewModel with a View when using Onyx. There's several approaches to doing this, but the Onyx has a preferred method. Here's a snippet of XAML taken directly from the "Simple" sample application
that illustrates how it's done.
Title="Simple" Height="300" Width="300"
property is used to specify the ViewModel
that is associated with a
. Any object may be specified here, but if you specify a Type
, as was done here, then the
will be constructed for you. How the instance is constructed is a little complex, and can even be modified by setting
to a custom IViewModelActivator
, but for now we'll concentrate on the default behavior in the most typical usage. In our "Simple" sample application the
has a public constructor that takes a single argument of type
. This is the constructor that's used to construct our ViewModel
in this case.
Where does the View
that's passed to the constructor come from? There's a static method,
, which can be called to obtain the View
associated with any
. If a View
isn't currently associated with the
, one will be created and associated at that time. The
instance provides two important things: a reference to the ViewElement
(the DependencyObject that the
is associated with), and an implementation of IServiceProvider
. While it's possible for a
to interact with the ViewElement
, this generally would cause a tight coupling, and so you should be very careful about doing this. Instead, you generally should interact via services provided through the
implementation. For example, the "Simple" sample application displays a message box to the user by obtaining an
service from the View
Where do the services available from the View
come from? They come from one of three places, in the following order:
- If the ViewElement implements an interface, that interface is available as a service.
- If the ViewElement implements IServiceProvider, GetService will be called on the
- The OnyxContainer associated with the View instance is the final source for the service.
is populating with common services when the View
is constructed. You can even add your own common services by subscribing to the
event. All of this provides you with a great deal of flexibility and power when it comes to registering services that will be found by the
provided to the ViewModel
. By default, Onyx provides several common services based on the type of the