Note: The question specifically mentions WPF and XAML, so this is a WPF and XAML-specific answer based on a WPF app I've worked on, with similar requirements to customise isolated parts of the UI. The general approach might apply to other UI frameworks with some extra work.
Firstly, creating a complete Front-end on a per-customer basis, while possibly undesirable, is not a completely invalid option, even if it results in a lot of copy+paste code with a lot of duplication in your UI layer. if this option is the easiest, simplest, and quickest way to get you to a deadline, then it might be your "best" option.
An option which reduces duplication is to create a modular front-end where you can customise individual components rather than complete pages, and design the app so that those components can be swapped in and out.
Microsoft's Prism Developer Guide
I strongly recommend you read Microsoft's Prism Developer Guide even if you are not going to use the Prism libraries.
(The name 'Prism' might be a little confusing because it's the name often used to describe the Libraries. The Guidelines are mostly applicable to any MVVM application with or without those libraries).
Microsoft's Patterns and Practices team recommend you create separate View
s for each area of end-user functionality, and also suggest that you write your top-level layout separately those View
s. i.e. Views which contain your generic or customer-specific controls and behaviour exist as entirely self-contained components.
(Chapter 7 explains this better than I could: https://msdn.microsoft.com/en-us/library/ff921098(v=pandp.40).aspx)
In short:
Views contain your actual UI functionality - controls, bindings, commands, triggers, templates. ViewModels, which should be tailored to individual views, contain the state of that UI. A View
and ViewModel
often has a one-to-one relationship.
Layout is typically your top-level Window
which is purely structural and totally agnostic to any of the controls. Prism has a very useful concept called RegionManager
which can do the hard work in connecting a View
to the 'right' area of your Layout.
Creating a modular front-end will not necessarily eliminate duplication entirely, but should reduce it to a point where it's easily manageable and mostly irrelevant.
For example, consider a simple single-page WPF UI with the following functional elements:
- Search Box + Button with filter options
- Results list/table with headings
- Editable item properties box
- Statistics box
In a small, simple WPF app, you might end up with the following components by default:
FilteredSearchBoxControl / FilteredSearchBoxViewModel
ResultsTableControl / ResultsTableViewModel
EditablePropertiesControl / EditablePropertiesViewModel
StatisticsBoxControl / StatisticsBoxViewModel
MainWindowShell / MainWindowViewModel
ApplicationCoreLogic
Creating the "glue" to wire all this together can be nontrivial, so if you use the Prism library itself, you get access to the tools which make a lot of this easier to do, for example:
EventAggregator
- Message-based communication between decoupled components and ViewModels.
Bootstrapper
- base class for an IoC Container and some default registrations (such as the EventAggregator or RegionManager)
AutoWireViewModel
allows XAML to hook into the IoC container for creating ViewModels without a default constructor (Avoids a messy Code-Behind).
RegionManager
- manages view-switching in specific areas of the layout.
Some other suggestions:
Avoid putting your general-purpose logic into ViewModels
, because replacing a View
for a specific customer will probably mean writing a new ViewModel for that replacement View. (There's nothing wrong with creating very lightweight ViewModels under the MVVM pattern). Instead, put generic, non-customer-specific business or app logic in separate classes which are unconnected to the UI (application/business layer "stuff").
Create a customer-specific View
for those areas of the app which need to change instead of changing the whole front end. For example, you might want a MyCustomerFilteredSearchBox
with a new tickbox and an extra button - you can just add that View
to the app without touching any existing UI code.
Create your own back-end application logic/configuration to connect specific customer controls to specific customers, and determine which configuration to use. Use the RegionManager
to make sure that the right Views appear.
If you are writing a larger, more complex app, or need finer granularity than simply swapping Views at the top-level Shell, you can create Scoped Regions (essentially "nested" regions) - https://msdn.microsoft.com/en-gb/library/ff921162.aspx