Catel 3 7 Documentation
Catel 3 7 Documentation
4
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.1 Why Catel? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.2 Platform support explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.3 Introduction to data objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.4 Introduction to MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.1.4.1 MVVM framework comparison sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.1.4.2 Different interpretations of MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.1.4.3 Validation in model or view model? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.1.4.4 Introduction to MVVM and models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.1.4.5 Creating view models with Catel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.1.4.6 Introduction to services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.1.4.7 Introduction to the nested user controls problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.1.4.8 Introduction to unit testing in MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.1.5 Introduction to MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.2 FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.2.1 General . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.2.2 MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.3 Setup, deployment and projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
1.3.1 Getting prerelease (beta) versions via NuGet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
1.3.2 Stepping through the code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
1.3.3 Compiling from source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
1.3.4 Code snippets & templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
1.3.4.1 Using the code snippets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
1.3.4.2 Using the item templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.3.4.3 Using the project templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.3.5 Updating to a new version via NuGet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.4 Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.4.1 Quick introduction for developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.4.2 Getting started with an empty MVVM application using WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.4.3 Getting started with MVVM and WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.4.4 Getting started with MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.4.5 Getting started with the core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.5.1 WPF Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.5.1.1 Advanced WPF example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.5.1.2 Authentication example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1.5.1.3 Browser application example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
1.5.1.4 Master/detail example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.5.1.5 Multilingual example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.5.1.6 MVVM communication styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
1.5.1.7 Person application WPF example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
1.5.1.8 Validation example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
1.5.1.9 Viewmodel lifetime example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
1.5.2 Silverlight examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
1.5.2.1 Advanced Silverlight example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
1.5.2.2 Navigation example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
1.5.2.3 Nested user controls example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
1.5.2.4 Person application Silverlight example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
1.5.3 Windows Phone examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
1.5.3.1 Bing maps example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
1.5.3.2 Sensors example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
1.5.3.3 Shopping list example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
1.6 Performance considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
1.7 Catel.Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
1.7.1 Argument checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
1.7.2 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
1.7.3 Data handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
1.7.3.1 ObservableObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
1.7.3.2 DispatcherObservableObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
1.7.3.3 ModelBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
1.7.3.4 Using ModelBase as base for entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
1.7.3.5 Advanced property change notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
1.7.3.6 WCF services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
1.7.4 Exception handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
1.7.5 IoC (ServiceLocator and TypeFactory) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
1.7.5.1 Introduction to the ServiceLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
1.7.5.2 Introduction to the TypeFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
1.7.5.3 Introduction to DependencyResolver and DependencyResolverManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
1.7.5.4 Dependency injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
1.7.5.5 Ensuring integrity of the ServiceLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
1.7.5.6 Synchronization with external containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
1.7.5.7 Using MEF as primary IoC container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
1.7.5.8 Using Unity as primary IoC container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
1.7.5.9 Setting up the ServiceLocator using configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
1.7.5.10 Automatically registering types using attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
1.7.6 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
1.7.6.1 Customizing listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
1.7.6.2 Batch log listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
1.7.6.3 Integration with external loggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
1.7.6.3.1 Log4net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
1.7.6.3.2 NLog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
1.7.6.4 Writing logs to disk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
1.7.7 Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
1.7.7.1 MessageBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
1.7.7.2 Message mediator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
1.7.7.3 Messaging via attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
1.7.8 Preventing memory leaks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
1.7.8.1 Change notification wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
1.7.8.2 Weak events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
1.7.9 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
1.7.10 Serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
1.7.10.1 Introduction to serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
1.7.10.2 Specifying what gets serialized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
1.7.10.3 Customizing serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
1.7.11 Thread safe code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
1.7.12 Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
1.7.12.1 Validation via validate methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
1.7.12.2 Validation via data annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
1.7.12.3 Validation via special model validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
1.7.12.4 Validation via IValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
1.7.12.5 Using the validation context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
1.7.12.6 Getting a summary of validation results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
1.7.12.7 Deferring validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
1.7.13 Scoping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
1.8 Catel.MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
1.8.1 Auditing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
1.8.2 Behaviors & triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
1.8.2.1 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
1.8.2.2 DelayBindingUpdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
1.8.2.3 DoubleClickToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
1.8.2.4 EventToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
1.8.2.5 Focus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
1.8.2.6 FocusFirstControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
1.8.2.7 KeyPressToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
1.8.2.8 MouseInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
1.8.2.9 Navigate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
1.8.2.10 NumericTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
1.8.2.11 SelectTextOnFocus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
1.8.2.12 UpdateBindingOnPasswordChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
1.8.2.13 UpdateBindingOnTextChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
1.8.3 Commands & events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
1.8.3.1 Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
1.8.3.2 Asynchronous commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
1.8.3.3 Commands authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
1.8.3.4 Hooking a command to validation automatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
1.8.4 Converters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
1.8.5 Designers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
1.8.6 Handling application initialization parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
1.8.7 Locators and naming conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
1.8.7.1 ViewModelLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
1.8.7.2 ViewLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
1.8.7.3 UrlLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
1.8.7.4 Naming conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
1.8.8 Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
1.8.8.1 AccelerometerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
1.8.8.2 CameraService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
1.8.8.3 CompassService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
1.8.8.4 GyroscopeService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
1.8.8.5 LocationService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
1.8.8.6 MessageService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
1.8.8.7 NavigationService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
1.8.8.8 OpenFileService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
1.8.8.9 PleaseWaitService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
1.8.8.10 ProcessService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
1.8.8.11 SaveFileService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
1.8.8.12 SchedulerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
1.8.8.13 SelectDirectoryService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
1.8.8.14 SplashScreenService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
1.8.8.15 UIVisualizerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
1.8.8.16 VibrateService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
1.8.8.17 ViewExportService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
1.8.9 View models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
1.8.9.1 Creating a basic view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
1.8.9.2 Creating a view model that watches over other view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
1.8.9.3 Creating a view model with a model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
1.8.9.4 Creating a view model with a model and mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
1.8.9.5 Mapping properties from view to view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
1.8.9.6 Nested view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
1.8.9.7 Validation in view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
1.8.9.8 Advanced view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
1.8.9.8.1 Keeping view models alive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
1.8.9.8.2 Exposing properties of a model automatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
1.8.9.8.3 Determine the view model type dynamically at runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
1.8.9.8.4 Controlling the instantiation of view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
1.8.10 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
1.8.10.1 DataWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
1.8.10.2 UserControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
1.8.10.3 MVVM behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
1.8.10.4 Validation controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
1.8.10.5 Finding the view of a view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
1.8.10.6 Using external controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
1.8.10.6.1 Using a custom control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
1.8.10.6.2 Using a custom window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
1.8.10.7 Advanced information about views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
1.8.10.7.1 DataWindow - under the hood . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
1.8.10.7.2 UserControl - under the hood . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
1.9 Catel.Mvc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
1.9.1 Configuring dependency injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
1.10 Catel.Extensions.Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
1.10.1 Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
1.10.1.1 StackGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
1.10.1.2 TabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
1.10.1.3 TraceOutputControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
1.10.1.4 WatermarkTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
1.10.2 Pixel shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
1.10.3 StyleHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
1.10.4 Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
1.11 Catel.Extensions.CSLA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
1.12 Catel.Extensions.Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
1.12.1 Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
1.13 Catel.Extensions.DynamicObjects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
1.14 Catel.Extensions.EntityFramework5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
1.14.1 Using the DbContextManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
1.14.2 Using the repositories and unit of work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
1.15 Catel.Extensions.FluentValidation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
1.16 Catel.Extensions.Interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
1.16.1 Method interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
1.16.2 Property interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
1.17 Catel.Extensions.Memento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
1.17.1 Memento and collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
1.17.2 Memento and methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
1.17.3 Memento and properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
1.18 Catel.Extensions.Prism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
1.18.1 Declaring modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
1.18.2 Translating or customizing the initialization task messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
1.18.3 Using the bootstrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
1.19 Catel.Fody . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
1.19.1 Weaving properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
1.20 Catel.ReSharper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
1.20.1 Checking arguments of a method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
1.20.2 Converting regular properties into Catel properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
1.21 Reference documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
Catel documentation Home
Welcome to the documentation of Catel.The framework consists of several projects. Each project has it's reference documentation. The actual
technical documentation is available in the downloads sections. However, the wiki contains some real-life examples and additional explanation of
some of the features of Catel.
Supported platforms
Looking for documentation of other versions?
Articles or documentation?
Explanation about the documentation
Catel is an application toolkit with the focus on MVVM, and consists of two basic pillars and extensions.
contains an IoC container, data objects, validation, message mediator, argument checking, etc. This can be used in any application Catel.Core
and is not specific for Windows applications.
Catel.MVVM contains all the MVVM classes such as ViewModelBase, Command, services, etc.
Catel also provides the following extensions:
Catel.Extensions.Controls
Catel.Extensions.CSLA
Catel.Extensions.Data
Catel.Extensions.EntityFramework5
Catel.Extensions.FluentValidation
Catel.Extensions.Interception
Catel.Extensions.Memento
Catel.Extensions.Prism
Below is a visual representation of the available blocks in Catel:
Supported platforms
The following platforms are supported by Catel:
Don't want to read much? Make sure that you at least read the with the absolute basics quick introduction
Looking for documentation of other versions?
Catel LATEST
Catel 3.6
Catel 3.5
Articles or documentation?
You have probably also found the articles on The Code Project. These articles are meant to be an introduction to a specific field of techniques that
can be found inside Catel. If you are looking for documentation, this is the place to be.
Are you missing documentation?
If you are missing documentation or feel that documentation is out of date, please let us know and we will will fix it!
Explanation about the documentation
You can either navigate through the left bar or via the search at the right top. Below are the top parts of the documentation:
Introduction
FAQ
Setup, deployment and projects
Getting started
Examples
Performance considerations
Catel.Core
Catel.MVVM
Catel.Mvc
Catel.Extensions.Controls
Catel.Extensions.CSLA
Catel.Extensions.Data
Catel.Extensions.DynamicObjects
Catel.Extensions.EntityFramework5
Catel.Extensions.FluentValidation
Catel.Extensions.Interception
Catel.Extensions.Memento
Catel.Extensions.Prism
Catel.Fody
Catel.ReSharper
Reference documentation
Introduction
Welcome to the introduction of Catel. Catel is a framework (or enterprise library, just use whatever you like) with data handling, diagnostics,
logging, WPF controls, and an MVVM-framework. So Catel is more than "just" another MVVM-framework or some nice Extension Methods that
can be used. It's more like a library that you want to include in all the XAML applications you are going to develop in the near future.
It's important to realize that Catel is not just another Extension Methods library, nor only an MVVM-framework, but it is a combination of basic data
handling, useful controls, and an MVVM-framework.
Why another framework?
You might be thinking: why another framework, there are literally thousands of them out there. Well, first of all, thousands of them is quite a lot,
For detailed information about platform support, see Platform support explanation
Don't forget to take a look at the code snippets and project templates, they will make your life much easier
Note that Catel is primarily meant for Line of Business (LoB) applications
let's just say there are hundreds of them. A few years ago, the lead developer of Catel was using serialization to serialize data from/to disk. But,
as he noticed, he had to take care of different versions after every release. After every release, he had to take care of the serialization and
backwards compatibility. Also, he had to implement some very basic interfaces (such as INotifyPropertyChanged) for every data object. Then, he
decided to write a base class for data handling which can take care of different versions and serialization by itself, and implements the most basic
interfaces of the .NET Framework out of the box. The article was published on CodeProject as DataObjectBase.
Then, he was working on a WPF project with five other developers and needed an MVVM-framework since the application was not using MVVM
at the moment. Writing an MVVM-framework was no option because there were so many other frameworks out there. But, after looking at some
Open-Source MVVM-frameworks (such as the excellent Cinch framework, which was the best one we could find), none of them seemed to be a
real option. Creating the View Models was too much work, and the View Models still contained lots of repetitive code in, for example, the property
definitions. After taking a closer look at the source code of Cinch and other frameworks, the lead developer thought: if we use the DataObjectBase
published before as the base for a View Model class, it should be possible to create a framework in a very short amount of time.
Then, all other developers of the team he was working on the project got enthusiastic, and then the whole team decided to merge their personal
libraries into one big enterprise library, and Catel was born.
Why use this framework?
Before reading any further, it's important to know why you should use the framework. Below are a few reasons why Catel might be interesting for
you:
Catel is Open-Source. This way, you can customize it any way you want. If you want a new feature request, and the team does not
respond fast enough, you can simply implement it yourself.
The codebase for Catel is available on GitHub. This way, you have the option to either download the latest stable release, or live on the
edge by downloading the latest source code.
Catel uses unit tests to make sure that new updates do not break existing functionality.
Catel is very well documented. Every method and property has comments, and the comments are available in a separate reference help
file. There is also a lot of documentation available, and in the future, in-depth articles will be written.
Catel is developed by a group of talented software developers, and is heavily under development. This is a great advantage because the
knowledge is not at just one person, but at a whole group. The developers of Catel all have more than three years of development
experience with WPF, and are using Catel in real life applications for at least 2 years.
Continue reading
Why Catel?
Platform support explanation
Introduction to data objects
Introduction to MVVM
Introduction to MVC
Why Catel?
We care a lot about the freedom you need as a software developer. Most frameworks require a developer to learn its conventions, or use the
whole framework or nothing at all. When we, the developers of Catel, use an external framework, we choose that framework for a specific reason,
and dont want to be bothered by all the other superb stuff it has to offer (maybe later, but not now).
During the development of Catel, we tried to maintain this freedom aspect which is very important to us. Therefore, all functionality is loosely
coupled. Sounds great, but everything is called loosely coupled nowadays. Catel contains a lot of different aspects, such as logging, diagnostics,
Reflection, MVVM, user controls, windows, etc. All these aspects are complementary to each other, but the great thing about Catel is that you
decide whether to use just one, some, or maybe all aspects.
As an example: you have a legacy application and want to use the DataWindow to write simple entry windows, but you are not ready for MVVM
yet. No problem, the DataWindow is complementary to MVVM, but does not require it. Therefore, you have all the freedom to use just what you
need, whenever you need it.
Most frameworks require a bootstrapper that completely decides how your application structure should look like. For example, your Views must
have this name, your controls must have that name. Again, in Catel, we wanted to give you the freedom you would expect from a framework.
The great thing about this freedom is that the different aspects of Catel can be used side-by-side with other frameworks, so you as a developer
can use the best framework for every aspect in your application.
Another nice thing is that Catel is WPF-based. Now you might be thinking: but hey, I use Silverlight! However, the upside of this approach is that
all goodies that are well known in WPF, but not in Silverlight are also brought to Silverlight. For example, in Silverlight there is no automatic
command re-evaluation. Catel does this out of the box, even in Silverlight.
Catel offers a solution in the following fields:
Data handling
MVVM
And much more which you will find out during the use of Catel!
Platform support explanation
This page explains in detail what parts of Catel are available on specific platforms.
/// <summary>
/// Initializes a new instance of the <see cref="Room"/> class.
/// </summary>
/// <param name="name">The name.</param>
public Room(string name)
{
// Create collections
Tables = new ObservableCollection<Table>();
Beds = new ObservableCollection<Bed>();
// Store values
Name = name;
}
/// <summary>
/// Initializes a new object based on <see cref="SerializationInfo"/>.
/// </summary>
/// <param name="info"><see cref="SerializationInfo"/> that contains the
information.</param>
/// <param name="context"><see cref="StreamingContext"/>.</param>
protected Room(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endregion
#region Properties
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name
{
get { return GetValue<string>(NameProperty); }
set { SetValue(NameProperty, value); }
}
/// <summary>
/// Register the Name property so it is known in the class.
/// </summary>
public static readonly PropertyData NameProperty = RegisterProperty("Name",
typeof(string), "Room");
/// <summary>
/// Gets or sets the table collection.
/// </summary>
public ObservableCollection<Table> Tables
{
get { return GetValue<ObservableCollection<Table>>(TablesProperty); }
set { SetValue(TablesProperty, value); }
}
/// <summary>
/// Register the Tables property so it is known in the class.
/// </summary>
public static readonly PropertyData TablesProperty = RegisterProperty("Tables",
typeof(ObservableCollection<Table>));
/// <summary>
/// Gets or sets the bed collection.
/// </summary>
public ObservableCollection<Bed> Beds
{
get { return GetValue<ObservableCollection<Bed>>(BedsProperty); }
set { SetValue(BedsProperty, value); }
}
/// <summary>
/// Register the Beds property so it is known in the class.
/// </summary>
public static readonly PropertyData BedsProperty = RegisterProperty("Beds",
typeof(ObservableCollection<Bed>));
#endregion
}
Next, we are going to create the view model. Again, by the use of code snippets explained earlier in this article, the view model is set up within a
few minutes:
/// <summary>
/// Room view model.
/// </summary>
public class RoomViewModel : ViewModelBase
{
#region Variables
private int _bedIndex = 1;
private int _tableIndex = 1;
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new instance of the <see cref="RoomViewModel"/> class.
/// </summary>
public RoomViewModel(Models.Room room)
{
// Store values
Room = room;
// Create commands
AddTable = new Command(OnAddTableExecuted);
AddBed = new Command(OnAddBedExecuted);
}
#endregion
#region Properties
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "Room"; } }
#region Models
/// <summary>
/// Gets or sets the room.
/// </summary>
[Model]
public Models.Room Room
{
get { return GetValue<Models.Room>(RoomProperty); }
private set { SetValue(RoomProperty, value); }
}
/// <summary>
/// Register the Room property so it is known in the class.
/// </summary>
public static readonly PropertyData RoomProperty = RegisterProperty("Room",
typeof(Models.Room));
#endregion
#region View model
/// <summary>
/// Gets or sets the name.
/// </summary>
[ViewModelToModel("Room")]
public string Name
{
get { return GetValue<string>(NameProperty); }
set { SetValue(NameProperty, value); }
}
/// <summary>
/// Register the Name property so it is known in the class.
/// </summary>
public static readonly PropertyData NameProperty = RegisterProperty("Name",
typeof(string));
/// <summary>
/// Gets or sets the table collection.
/// </summary>
[ViewModelToModel("Room")]
public ObservableCollection<Models.Table> Tables
{
get { return GetValue<ObservableCollection<Models.Table>>(TablesProperty); }
set { SetValue(TablesProperty, value); }
}
/// <summary>
/// Register the Tables property so it is known in the class.
/// </summary>
public static readonly PropertyData TablesProperty = RegisterProperty("Tables",
typeof(ObservableCollection<Models.Table>));
/// <summary>
/// Gets or sets the bed collection.
/// </summary>
[ViewModelToModel("Room")]
public ObservableCollection<Models.Bed> Beds
{
get { return GetValue<ObservableCollection<Models.Bed>>(BedsProperty); }
set { SetValue(BedsProperty, value); }
}
/// <summary>
/// Register the Beds property so it is known in the class.
/// </summary>
public static readonly PropertyData BedsProperty = RegisterProperty("Beds",
typeof(ObservableCollection<Models.Bed>));
#endregion
#endregion
#region Commands
/// <summary>
/// Gets the AddTable command.
/// </summary>
public Command AddTable { get; private set; }
/// <summary>
/// Method to invoke when the AddTable command is executed.
/// </summary>
private void OnAddTableExecuted()
{
Tables.Add(new Models.Table(string.Format("Table {0}", _tableIndex++)));
}
/// <summary>
/// Gets the AddBed command.
/// </summary>
public Command AddBed { get; private set; }
/// <summary>
/// Method to invoke when the AddBed command is executed.
/// </summary>
private void OnAddBedExecuted()
{
Beds.Add(new Models.Bed(string.Format("Bed {0}", _bedIndex++)));
}
#endregion
}
As you can see, the view model can only be constructed by passing a model object. It is very important to be aware of this construction. Room
The reason that there is no empty constructor is because there is no support for views that do not represent a model. Room
In the view model, the properties of the Room model are mapped by the use of the attribute and the attribute. Last but Model ViewModelToModel
not least, commands are defined to be able to add new tables and beds to the model. Room
Now the model and the view model are fully set up, the last thing to do is to create the actual view. To accomplish this, add a new WPF user
control to the project. First thing to do is to implement the code-behind, since that is the easiest to do:
<summary>
/// Interaction logic for Room.xaml
/// </summary>
public partial class Room : UserControl
{
/// <summary>
/// Initializes a new instance of the <see cref="Room"/> class.
/// </summary>
public Room()
{
// Initialize component
InitializeComponent();
}
}
The only thing we changed from the default user control template is that the user control now derives from the generic control instead UserControl
of the default control. Then, the view model that the user control should use is provided as generic System.Windows.Controls.UserControl
argument. This is it for the code-behind, lets move up to the view.
The last thing to do now is the actual xaml view. For the sake of simplicity, the actual content is left out (its just a grid with a textbox and
itemscontrols for the children):
<catel:UserControl
x:Class="Catel.Articles._03___MVVM.Examples.NestedUserControls.Room"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:NestedUserControls="clr-namespace:Catel.Articles._03___MVVM.Examples.NestedUserC
ontrols">
<!-- For the sake of simplicity, the content is left out -->
</catel:UserControl>
A few things are very important to notice in the xaml code shown above. The first thing to notice is that (like the code-behind), the base class is
now instead of . catel:UserControl UserControl
Thats all that can be learned about solving the nested user control problem. We have set up the model, view model and finally the view. Now,
lets take a look at how it looks in a screenshot (and notice the construction time of the view model, they are really constructed on-demand):
Another way to add a new user control is to use the item templates
The red border is the control that we just created. It shows the name of the room, the view model construction time and the child objects (inside
expanders).
1.
2.
3.
Introduction to unit testing in MVVM
If you are wondering why you should even write unit tests, you should also wonder why you arent still living in a cage and writing stuff on the
walls (don't feel offended, continue reading...). Writing unit tests makes sure that you dont break existing functionality in an application when
making changes. This lowers the cost of QA (since you dont need a technical tester executing regression tests all the time). I dont say that a
tester isnt needed; my opinion is that at least someone else besides the developer should take a human look at the software before it is even
released. If you are still not convinced why you should write unit tests, please go back to your cave and stop reading this article for the sake of
your own pleasure.
This documentation does not cover the basics of unit testing. It assumes that you already know what unit tests are, and how to write them. This
documentation is specifically written to explain how view models of the MVVM pattern can be unit tested,especially with the use of Catel.
Testing commands
Thanks to commands (which implement the interface), testing view models and UI logic has never been so easy. Now commands can ICommand
be invoked programmatically without having to automate the UI; it is very simple to reproduce a button click during a unit test.
When testing commands, it is very important to test the state as well. The command implementation of Catel has a and an CanExecute Execute
method which can be invoked manually. Therefore, it is very easy to test a command. The code below shows a test that checks whether the Rem
command can be executed. At first, the command cannot be executed because there is no selected person. After the selected person is set, ove
the command should be able to execute:
Assert.IsFalse(mainWindowViewModel.Remove.CanExecute(null));
mainWindowViewModel.SelectedPerson = mainWindowViewModel.PersonCollection[0];
Assert.IsTrue(mainWindowViewModel.Remove.CanExecute(null));
To execute a command in code, one should use the following code:
mainWindowViewModel.Remove.Execute(null);
Testing services
For more information about unit testing services, see the unit testing services documentation.
Introduction to MVC
FAQ
Welcome to the FAQ. Please pick a section:
General
MVVM
General
I want to change the bitmap effects, what should I do?
StyleHelper.CreateStyleForwardersForDefaultStyles does not work in Silverlight
I want to change the bitmap effects, what should I do?
Download the DirectX SDK
Compile the .fx file into a .ps file using the following command: "$(DXSDKDIR)Utilities\Bin\x86\fxc.exe" /T ps2_0 /E main /Fo
"MyEffect.ps" "MyEffect.fx"
Now, the fx file is updated and Catel should be recompiled
StyleHelper.CreateStyleForwardersForDefaultStyles does not work in Silverlight
Probably, the application resources are defined like this:
Note that this documentation has to be written
1.
2.
3.
4.
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp">
<Application.Resources>
<ResourceDictionary Source="/CWP.Shared;component/themes/generic.xaml" />
</Application.Resources>
</Application>
However, Silverlight does not allow to add resources to a where the source is set. To be able to use default styles, use this ResourceDictionary
code:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyApp">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyApp.Shared;component/themes/generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MVVM
How to support example data with view models?
How to use events with MVVM?
Silverlight throws error about type that cannot be created?
How can I add the MVVM behaviors via code (programmatically)?
How can I inject or manipulate the view model of a UserControl?
How can I prevent validation of required fields?
How to support example data with view models?
To find out how to create design time data, see the topic. designers
How to use events with MVVM?
When writing MVVM, it's "forbidden" (read: not a best practice) to use click handlers (or other UI events) in your view-model. But then should you
react to events?
Start with creating a command like you are used to using MVVM. This command will be executed when the event occurs.
Add a reference to (ships with Catel). If you have used NuGet to add a reference, it is automatically System.Windows.Interactivity.dll
included for you.
Add the following namespace definitions to your view declaration:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Inter
activity"
xmlns:catel="http://catel.codeplex.com"
Use the following code to convert an event to a command:
4.
<i:Interaction.Triggers>
<i:EventTrigger EventName="[YourEvent]">
<catel:EventToCommand Command="{Binding [YourCommand]}"
DisableAssociatedObjectOnCannotExecute="False" />
</i:EventTrigger>
</i:Interaction.Triggers>
An example for a double click: ListBox
<ListBox ItemsSource="{Binding PersonCollection}" SelectedItem="{Binding
SelectedPerson}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<catel:EventToCommand Command="{Binding Edit}"
DisableAssociatedObjectOnCannotExecute="False" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding FirstName}" />
<Label Content="{Binding MiddleName}" />
<Label Content="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Silverlight throws error about type that cannot be created?
If you are using type creation via xaml this way:
<i:Interaction.Behaviors>
<catel:WindowBehavior ViewModelType="viewmodels:DemoWindowViewModel"
Save="okButton.Click" Cancel="cancelButton.Click" />
</i:Interaction.Behaviors>
It might be possible that Silverlight throws an exception with these details:
{System.Windows.Markup.XamlParseException: Failed to create a 'System.Type' from the
text 'ViewModels:DemoWindowViewModel'. [Line: 13 Position: 67]
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at Catel.Examples.AdvancedDemo.Views.LogicInBehavior.DemoWindow.InitializeComponent()
at Catel.Examples.AdvancedDemo.Views.LogicInBehavior.DemoWindow..ctor()}
This happens in Silverlight 4 when the Silverlight 5 (beta) tools are not installed. To resolve this issue, install the Silverlight 5 (beta) tools.
How can I add the MVVM behaviors via code (programmatically)?
Silverlight has a known issue, and sometimes installing the Silverlight 5 (beta) toolkit isn't the right option to solve the issue. Luckily, it is also
possible to add one of the MVVM behaviors via code to any control or window.
Below is the code-behind of a view that adds the via code: UserControlBehavior
public partial class DynamicBehaviorView : UserControl, IViewModelContainer
{
private Catel.Windows.Controls.MVVMProviders.UserControlBehavior _mvvmBehavior;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicBehaviorView"/> class.
/// </summary>
public DynamicBehaviorView()
{
InitializeComponent();
_mvvmBehavior = new Catel.Windows.Controls.MVVMProviders.UserControlBehavior();
_mvvmBehavior.ViewModelType = typeof(ViewModels.MyViewModel);
System.Windows.Interactivity.Interaction.GetBehaviors(this).Add(_mvvmBehavior);
_mvvmBehavior.ViewModelChanged += (sender, e) =>
ViewModelChanged.SafeInvoke(this, e);
_mvvmBehavior.ViewModelPropertyChanged += (sender, e) =>
ViewModelPropertyChanged.SafeInvoke(this, e);
}
/// <summary>
/// Gets the view model that is contained by the container.
/// </summary>
/// <value>The view model.</value>
public IViewModel ViewModel
{
get { return _mvvmBehavior.ViewModel; }
}
/// <summary>
/// Occurs when the <see cref="ViewModel"/> property has changed.
/// </summary>
public event EventHandler<EventArgs> ViewModelChanged;
/// <summary>
/// Occurs when a property on the <see cref="ViewModel"/> has changed.
/// </summary>
public event EventHandler<PropertyChangedEventArgs> ViewModelPropertyChanged;
#if !SILVERLIGHT
/// <summary>
/// Occurs when a property on the container has changed.
/// </summary>
/// <remarks>
/// This event makes it possible to externally subscribe to property changes of a
<see cref="DependencyObject"/>
/// (mostly the container of a view model) because the .NET Framework does not
allows us to.
/// </remarks>
public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
/// <summary>
/// Invoked whenever the effective value of any dependency property on this <see
cref="T:System.Windows.FrameworkElement"/> has been updated. The specific dependency
property that changed is reported in the arguments parameter. Overrides <see
cref="M:System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPro
pertyChangedEventArgs)"/>.
/// </summary>
/// <param name="e">The event data that describes the property that changed, as
well as old and new values.</param>
protected override void
OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
PropertyChanged.SafeInvoke(this, new PropertyChangedEventArgs(e.Property.Name));
1.
2.
}
#endif
}
Using this technique, it is even possible to determine the view model of any view dynamically at runtime.
How can I inject or manipulate the view model of a UserControl?
The is a very powerful control. It allows lazy loaded dynamic view model construction. However, sometimes you just don't want the UserControl
user control to dynamically create the view model. Luckily, the user control instantiates a new view model with this logic:
The of the control can be injected into a constructor of the view model DataContext
The view model has an empty constructor
You can set the of the control to a view model, and this way "inject" a view model into a control instead of letting it be created first. In DataContext
fact, the user control first checks whether the is already a valid view model for the user control. If so, it keeps it that way. DataContext
How can I prevent validation of required fields?
Catel does not validate the properties with data annotations at startup. It will only validate the data annotations when properties change or when
the view model is about to be saved. This is implemented this way to allow a developer to show required fields with an asterisk (*) instead of
errors. If a developer still wants to initially display errors, only a single call has to be made in the constructor:
Validate(true, false);
If the validation is implemented in the models and not in the view model, set the to false. ValidateModelsOnInitialization
Getting started
The getting started series focuses on the main features of Catel.
Quick introduction for developers
Getting started with an empty MVVM application using WPF
Getting started with MVVM and WPF
Getting started with MVC
Getting started with the core
Quick introduction for developers
This is a quick introduction for developers who don't have a lot of time to read all the docs. This document contains the absolute basics of what a
developer needs to know.
Core
Logging / debugging
Catel properties
MVVM
Handling of viewmodels
Handling hierarchy and parent/child view models
Communication between view models
Resolving views and view models
Core
This pare contains the core functionality of Catel and what you should know when using Catel.
Logging / debugging
If you ever think Catel is behaving strange or does not work as expected, make sure to enable the logging. Below is an example on how to enable
the logging:
#if DEBUG
LogManager.RegisterDebugListener();
#endif
Catel will then log everything to the output window and provide all the information about its internals.
For more information, read about . logging
Catel properties
All properties in classes deriving from (thus also ) require a special property definition. ModelBase ViewModelBase
Normally one would write something like this:
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
RaisePropertyChanging("FirstName");
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
In Catel one should write this:
public string FirstName
{
get { return GetValue< string>(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
public static readonly PropertyData FirstNameProperty = RegisterProperty("FirstName",
typeof(string), null);
Catel will automatically take care of change notifications.
MVVM
This part is especially meant for the MVVM part.
Handling of viewmodels
In other MVVM frameworks, you are obliged to set the data context of a view manually. It will look something like this:
var view = new PersonView();
view.DataContext = new PersonViewModel();
Note that you can use the or to easily create these properties using code snippets. You can also use inste dataprop vmprop Catel.Fody
ad
Catel automatically resolves the right view model based on the view. If a view is created, Catel automatically creates the view model:
var view = new PersonView();
// view model is automatically created
It goes even further. Catel can create view models based on the data context. For more information, read . nested user controls
Handling hierarchy and parent/child view models
Note thatCatelis already fully aware of parent/child relations of view models so you dont have to do anything
for this yourself. For more information,read . nested user controls
Communication between view models
There are available to communicate between view models. Just make sure that you never directly reference other view model several methods
and keep everything loosely coupled.
Resolving views and view models
Catel resolves views and view models by naming convention. This means that based on the name of a view, the view model can be determined.
This also works the other way around where the view model can be determined based on the view. For more information, read about naming
. conventions
Getting started with an empty MVVM application using WPF
This getting started guide explains how to create your first WPF project using Catel. It is a combination of using the project and item templates
together with some custom logic. It assumes that you have installed both the Setup package as well as NuGet.
Creating the project
Start with an empty Visual Studio shell. Then, choose -> -> ... and choose the WPF project template (located under -> File New project Windows C
) atel
Most of the application is already created for you. Up to the next step!
Adding the references
Adding references to Catel has never been so easy using NuGet. No worries about the latest version, just add them and you're done! Right click
on the project, then choose . Manage NuGet packages
Then, search for (make sure to select the online gallery) and choose Catel.Extensions.Controls (which will automatically install a Catel Catel.Core
nd ): Catel.MVVM
Running the project
The templates has created a main window. Now the only thing to do is implement actual logic (we can't generate that for you)!
WPF Examples
Advanced WPF example
Authentication example
Browser application example
Master/detail example
Multilingual example
MVVM communication styles
Person application WPF example
Validation example
Viewmodel lifetime example
Silverlight examples
Advanced Silverlight example
Navigation example
Nested user controls example
Person application Silverlight example
Windows Phone examples
Bing maps example
Sensors example
Shopping list example
WPF Examples
Advanced WPF example
Authentication example
Browser application example
Master/detail example
Multilingual example
MVVM communication styles
Person application WPF example
Validation example
Viewmodel lifetime example
Advanced WPF example
This example shows advanced functionality such as MEF, Unity, Nested User Controls and MVVM behaviors.
The first groupbox at the left shows how to instantiate a Window both where the MVVM logic is implemented in the view base ( ) and DataWindow
a behavior ( ). WindowBehavior
The second groupbox shows how to instantiate the both where the MVVM logic is implemented in the view base ( ) and a UserControl UserControl
behavior ( ). UserControlBehavior
This guide will contain a full app
This guide must be written
This documentation needs to be written
If you have an example application that you want to share, let us know!
The third groupbox shows the usage of the (both in determinate and indeterminate mode) using the ServiceLocator, MEF and IPleaseWaitService
Unity.
Screenshots
Authentication example
This example shows how to use authentication for views. You can either choose to run the window as read-only user or as administrator. When
running as read-only user, you will notice that the controls are disabled, hidden and collapsed. When running as administrator, the controls will all
be available.
Screenshots
Browser application example
This example shows how to run a browser application in WPF by using the . INavigationService
Screenshots
Master/detail example
This example shows how to create a master/detail with a as detail which automatically responds to changes in the master. UserControl
Screenshots
Multilingual example
This example show how to use the multiple languages in Catel. This example is also used to test new languages that are translated for Catel.
Screenshots
MVVM communication styles
There are several ways to implement communication between view models. This example shows the different communication styles:
InterestedIn
MessageMediator
InterestedIn
The InterestedIn method in Catel is the easiest way to communicate between view models. This can be done by simply adding theInterestedInAtt
on top of a view model and override the and methods. ribute OnViewModelPropertyChanged OnViewModelCommandExecuted
MessageMediator
Thought more flexible, the technique requires more work because the developer is responsible for defining custom messages, MessageMediator
registering and unregistering specific messages and sending the messages via the . MessageMediator
Screenshots
Person application WPF example
This example is a small application that allows adding persons via a model popup window. Is shows how to add validation, modal dialogs (via
MVVM) and view model to model mappings.
Screenshots
Validation example
This example shows all the different validation techniques available in Catel. The following validation techniques are shown:
Validation via validate methods
Validation via data annotations
Validation in model
Validation in IValidator
Validation via FluentValidation Extension
Screenshots
Viewmodel lifetime example
This example shows how to manually control the lifetime of view models. When adding new tabs, you can either choose the default behavior
(close view model when a control is unloaded) or to keep the view model alive. When a tab is unselected, the view model will be closed because
the view is unloaded from the visual tree. When the control should keep the view model alive, you will note that the total number of view models
will not decrease when switching from tabs.
All view models are explicitly closed when the close button on the tab is clicked. In that case, you will see the total number of alive view models
decrease.
Screenshots
Silverlight examples
Advanced Silverlight example
Navigation example
Nested user controls example
Person application Silverlight example
Advanced Silverlight example
This example shows advanced functionality such as MEF, Unity, Nested User Controls and MVVM behaviors.
The first groupbox at the left shows how to instantiate a both where the MVVM logic is implemented in the view base ( ) and Window DataWindow
a behavior ( ). WindowBehavior
The second groupbox shows how to instantiate the both where the MVVM logic is implemented in the view base ( ) and a UserControl UserControl
behavior ( ). UserControlBehavior
The third groupbox shows the usage of the (both in determinate and indeterminate mode) using the , MEF and IPleaseWaitService ServiceLocator
Unity.
Screenshots
Navigation example
This example shows how to create a page navigation application using Silverlight. It uses the to implement navigation. INavigationService
Screenshots
Nested user controls example
This example shows how to use nested user controls without instantiating all the view models of the nested user controls first. As you can see by
the view model creation time, the view models are actually created on-demand (we also call this lazy loading of view models).
Screenshots
Person application Silverlight example
This example is a small application that allows adding persons via a model popup window. Is shows how to add validation, modal dialogs (via
MVVM) and view model to model mappings.
Screenshots
Windows Phone examples
Bing maps example
Sensors example
Shopping list example
ASP.NET application
In global.asax, add the following code:
var directory = Server.MapPath("~/bin");
AppDomain.Current.PreloadAssemblies(directory);
Warming up the serializers
To improve performance for serialization, . warm up the serializers
MVVM
Set SkipSearchingForInfoBarMessageControl on UserControl to true
By default, Catel assumes that an is located on any window. However, it might be that this control is not located on a InfoBarMessageControl
window that contains an instance of the class. This might decrease the performance, especially when lots of user controls are used in UserControl
a hierarchical way. The cause is that the searches for an to register the view model to. UserControlLogic InfoBarMessageControl
If no is located on a container, make sure to set to . InfoBarMessageControl SkipSearchingForInfoBarMessageControl true
Use the FastObservableCollection
The does not raise events for every item, but only invokes events for the complete range of items added to or removed FastObservableCollection
from the collection.
When modifying a large collection of items, it is not required to raise change events for each added / removed value. Therefore the FastObservabl
will disable change notifications until the full collection modification is done and then raise the change events just once. eCollection
Implementing IDependencyPropertySelector
Catel uses a special wrapping technology to wrap bindings to dependency properties to be able to add change notifications for all target
platforms. Though this technology works great, it might have impact on performance and this is not always necessary. By implementing a custom
, developers can tweak the interesting dependency properties per type. IDependencyPropertySelector
It is best to always create a default dependency instead.
public void CustomDependencyPropertySelector : DependencyPropertySelector
{
public override bool MustSubscribeToAllDependencyProperties(Type
targetControlType)
{
return false;
}
}
Then register it in theServiceLocator:
ServiceLocator.Default.RegisterType<IDependencyPropertySelector,
CustomerDependencyPropertySelector>();
Even when the custom implementation returns an empty list, Catel will always subscribe to the depen IDependencyPropertySelector DataContext
dency property because it depends on that.
Specify throttling on the ViewModelBase
The allows the specify the throttling of the property change notifications. In normal situations it is best to directly raise property ViewModelBase
change notifications. However, when a lot of properties change a lot within a very short timeframe, it might be interesting to enable throttling. By
using throttling, the change notifications are not directly sent to the UI but instead added to a dictionary. Then each time the is ThottlingRate
reached, the change notifications are sent in batches to the view. If the same property has changed several times in a specific time frame, it will
only be raised once which might give a performance boost in very specific situations.
By default, throttling is disabled but can be enabled by setting the property: ThrottlingRate
ThrottlingRate = new TimeSpan(0, 0, 0, 0, 200);
By default Catel subscribes to all dependency properties to not cause breaking changes. It is however possible to override the
registration using the that ships with Catel FastDependencyPropertySelector
Catel.Core
Argument checking
Caching
Data handling
Exception handling
IoC (ServiceLocator and TypeFactory)
Logging
Messaging
Preventing memory leaks
Reflection
Serialization
Thread safe code
Validation
Scoping
Argument checking
It is best practice to always check if the input to a method is correct. If not, an exception should be thrown. Most people do not check for
exceptions correctly and lots of null reference exceptions inside a deep stacktrace are hard to solve.
Catel does check the input on every method. Normally, a check would look like this:
public void CheckForException(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
}
However, Catel extensively logs all behavior, thus all the checks started to look like this:
public void CheckForException(object obj)
{
if (obj == null)
{
Log.Debug("Argument 'obj' is null in CheckForException");
throw new ArgumentNullException("obj");
}
}
Handling input correctly in such a case takes a lot of space and repetitive code. Therefore the Argument class is developed. This way, it is very
simple to check for arguments:
public void CheckForException(object obj)
{
Argument.IsNotNull("obj", obj);
}
Or, if a range should be checked:
The example contains a demo that shows the impact of throttling AdvancedDemo
public void CheckForException(int myInt)
{
Argument.IsNotOutOfRange("myInt", myInt, 0, 10);
}
A final example is to check whether a type implements a specific interface:
public void CheckForException(object obj)
{
Argument.ImplementsInterface("obj", obj, typeof(INotifyPropertyChanged));
}
Caching
Caching is about improve applications performance. The most expensive performance costs of the applications are related with the data
retrieving, typically when this data requires to be moved a cross the network or loaded from disk. But some data have an slow changing behavior
(a.k.a non-volatile) and doesn't requires to be re-read with the same frequency of the volatile data.
So, to improve your application performance and handling this "nonvolatile" data from a pretty clean approach, Catel comes with a CacheStorage
class. Notice that the first generic parameter is the type of the key and the second the type of the value the will be store, just like <TKey, TValue>
a but CacheStorage isn't it just a Dictionary. This class allows you to retrieve data and storing it into the cache with Dictionary<TKey, TValue>
single statement and also helps you to handle expiration policy if you need it.
Initializing a cache storage
To initialize a cache storage field into your class use the follow code:
private readonly CacheStorage<string, Person> _personCache = new CacheStorage<string,
Person>(allowNullValues: true);
Retrieve data and storing into cache with single statement
To retrieve data and storing into a cache with a single statement use the follow code:
var person = _personCache.GetFromCacheOrFetch(Id, () => service.FindPersonById(Id));
When this statement is executed more than once times with the with the same key, the value will be retrieved from the cache storage instead from
the service call. The service call will be executed just the first time or if the item is removed from the cache manually or automatically due by the
expiration policy.
Using cache expiration policies
The cache expiration policies adds a removal behavior to the cache storage items. A policy signals that an item is expired to makes that cache
storage removes the item automatically.
A default cache expiration policy initialization code can be specified during cache storage initialization constructor:
CacheStorage<string, Person> _personCache = new CacheStorage<string, Person>(() =>
ExpirationPolicy.Duration(TimeSpan.FromMinutes(5)), true);
You can specify an specific expiration policy for an item when it's storing:
_personCache.GetFromCacheOrFetch(id, () => service.GetPersonById(id),
ExpirationPolicy.Duration(TimeSpan.FromMinutes(10)));
The default cache policy specified at cache storage initialization will be used if during item storing the expiration policy is not specified.
Build-in expiration policies
Catel comes with build-in expiration policies. They are listed in the follow table:
Expiration policy Type Description Initialization code sample
AbsoluteExpirationPolicy Time-base The cache item will expire on the
absolute expiration DateTime ExpirationPol
icy.Absolute(
new
DateTime(21,
12, 2012))
DurationExpirationPolicy Time-base The cache item will expire using
the duration TimeSpan to
calculate the absolute expiration
from DateTime.Now
ExpirationPol
icy.Duration(
TimeSpan.From
Minutes(5))
SlidingExpirationPolicy Time-base The cache item will expire using
the duration TimeSpan to
calculate the absolute expiration
from DateTime.Now, but
everytime the item is requested,
it is expanded again with the
specified TimeSpan
ExpirationPol
icy.Sliding(T
imeSpan.FromM
inutes(5))
CustomExpirationPolicy Custom The cache item will expire using
the expire function and execute
the reset action if is specified.
The example shows how create
an sliding expiration policy with a
custom expiration policy.
var
startDateTime
=
DateTime.Now;
var duration
=
TimeSpan.From
Minutes(5);
ExpirationPol
icy.Custom(()
=>
DateTime.Now
>
startDateTime
.Add(duration
), () =>
startDateTime
=
DateTime.Now)
;
CompositeExpirationPolicy Custom Combines several expiration
policy into a single one. It can be
configured to expires when any
policy expires or when all
policies expires.
new
CompositeExpi
rationPolicy(
).Add(Expirat
ionPolicy.Sli
ding(
TimeSpan.From
Minutes(5))).
Add(Expiratio
nPolicy.Custo
m(()=>...))
Implementing your own expiration cache policy
If the is not enough, you can implement you own expiration policy to makes that cache item expires triggered from a CustomExpirationPolicy
custom event. You are also able to add some code to reset the expiration policy if the item is read from the cache before it expires (just like Slidin
does). gExpirationPolicy
To implement an expiration cache policy use the follow code template:
public class MyExpirationPolicy : ExpirationCachePolicy
{
public MyExpirationPolicy():base(true)
{
}
public override bool IsExpired
{
get
{
// Add your custom expiration code to detect if the item expires
}
}
public override void OnReset()
{
// Add your custom code to reset the policy if the item is read.
}
}
Data handling
This part of the documentation is all about data handling the way it should that is available via Catel. Some parts are based on the article on Code
Project, but this documentation is more up-to-date.
The first thing that is important is that lots of developers lose way too much time writing custom serializable objects. Serialization is a field of
expertise, and only a handful of developers I know really master the serialization of objects (think of version changes of the assembly, class
changes (new or removed properties), etc.). Most developers think they master serialization by creating a object like the code BinaryFormatter
belows show:
var serializer = new BinaryFormatter();
var myObject = (MyObject)serializer.Deserialize(stream);
Most developers dont know that reflection breaks when:
You change the version number of your assembly;
You add or remove a property or field;
You add or remove an event.
And even if you know, it takes a lot of knowledge and courage to start beating the beast of burden. Like every developer, I also encountered this
and was writing backwards compatibility code until I had enough of it and decided to master the field of serialization. The result is the ModelBase
class, which can be used as a base class for all data objects that need to be held in memory and maybe serialized to disk (or a stream, or XML, or
...).
ObservableObject
DispatcherObservableObject
ModelBase
Using ModelBase as base for entities
Advanced property change notifications
WCF services
ObservableObject
The is a very lightweight class that only implements the and interfaces. This ObservableObject INotifyPropertyChanging INotifyPropertyChanged
class is ideal for simple objects that only need property notification. Below is an example:
The base constructor have a parameter to indicates if the policy can be reset. Therefore, is you call the base constructor with false then
the OnReset method will never called.
public class Person : ObservableObject
{
private string _firstName;
private string _middleName;
private string _lastName;
public Person(string firstName, string middleName, string lastName)
{
FirstName = firstName;
MiddleName = middleName;
LastName = lastName;
}
public string FirstName
{
get { return _firstName; }
set
{
RaisePropertyChanging(() => FirstName);
var oldValue = _firstName;
_firstName = value;
RaisePropertyChanged(() => FirstName, oldValue, value);
}
}
public string MiddleName
{
get { return _middleName; }
set
{
RaisePropertyChanging(() => MiddleName);
var oldValue = _middleName;
_middleName = value;
RaisePropertyChanged(() => MiddleName, oldValue, value);
}
}
public string LastName
{
get { return _lastName; }
set
{
RaisePropertyChanging(() => LastName);
var oldValue = _lastName;
_lastName = value;
RaisePropertyChanged(() => LastName, oldValue, value);
}
}
}
DispatcherObservableObject
Note that the is located in because it uses the DispatcherObservableObject Catel.MVVM IDispatcherService
The is a class that derives from the class. The only difference is that the DispatcherObservableObject ObservableObject DispatcherObservableO
will dispatch all property change notifications to the UI thread. Below is a class that uses the and is thread-safe bject DispatcherObservableObject
for the change notifications.
public class Person : DispatcherObservableObject
{
private string _firstName;
private string _middleName;
private string _lastName;
public Person(string firstName, string middleName, string lastName)
{
FirstName = firstName;
MiddleName = middleName;
LastName = lastName;
}
public string FirstName
{
get { return _firstName; }
set
{
RaisePropertyChanging(() => FirstName);
var oldValue = _firstName;
_firstName = value;
RaisePropertyChanged(() => FirstName, oldValue, value);
}
}
public string MiddleName
{
get { return _middleName; }
set
{
RaisePropertyChanging(() => MiddleName);
var oldValue = _middleName;
_middleName = value;
RaisePropertyChanged(() => MiddleName, oldValue, value);
}
}
public string LastName
{
get { return _lastName; }
set
{
RaisePropertyChanging(() => LastName);
var oldValue = _lastName;
_lastName = value;
RaisePropertyChanged(() => LastName, oldValue, value);
}
}
}
ModelBase
Using the class
Defining properties
Default values for reference types
Functionality provided out of the box
The (previously known as the class is a generic base class that can be used for all your data classes. ModelBase DataObjectBase)
Fully serializable
It is now really easy to store objects on disk or serialize them into memory, either binary or in XML. The data object supports this out of
the box, and automatically handles the (de)serialization.
Support property changed notifications
The class supports the and interfaces so this class can easily be used in WPF, INotifyPropertyChanging INotifyPropertyChanged
Silverlight and applications to reflect changes to the user.
Backwards compatibility
When serializing your objects to binary, it is hard to maintain the right versions. When you add a new property to a binary class, or
change a namespace, the object cannot be loaded any longer. The data object base takes care of this issue and supports backwards
compatibility.
Validation
The class implements the interface so it is possible to validate the data object and check the errors. This way, no custom IDataErrorInfo
validation code needs to be written outside the data class.
Backup & revert
The class implements the interface which makes it possible to create a state of the object. Then all properties can be IEditableObject
edited, and finally, the changes can be applied or cancelled.
Using the class
Using the class is extremely simple. Just declare a new class that derives from and you are ready to go: ModelBase
/// <summary>
/// MyObject class which fully supports serialization,
/// property changed notifications, backwards compatibility and error checking.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
public class MyObject : ModelBase<MyObject>
{
/// <summary>
/// Initializes a new object from scratch.
/// </summary>
public MyObject() { }
#if !SILVERLIGHT
/// <summary>
/// Initializes a new object based on <see cref="SerializationInfo"/>.
/// </summary>
/// <param name="info"><see cref="SerializationInfo"/>
// that contains the information.</param>
/// <param name="context"><see cref="StreamingContext"/>.</param>
protected MyObject(SerializationInfo info, StreamingContext context)
: base(info, context) { }
#endif
}
As you can see in the code above, the class derives from and provides an empty constructor, but also a constructor that is MyObject ModelBase
used for binary deserialization. The code above might look complex, but it is created using the model code snippet, and you only have to type the
name of the class.
Defining properties
Defining properties for the class is very easy, and works the same like dependency properties. The advantages of this way of defining properties
are:
Properties defined like this are automatically included during serialization; no need to specify complex data contracts;
You can specify a default value for a property which will be used when the class is constructed or the property is not found during
deserialization (in case this property is added to an existing class);
The object can be used to retrieve property values so the compiler checks for errors; PropertyData
You can directly subscribe to change notifications, and all properties automatically support out of the box. INotifyPropertyChanged
Below is the code that defines a new property Name of type string:
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name
{
get { return GetValue<string>(NameProperty); }
set { SetValue(NameProperty, value); }
}
/// <summary>
/// Register the Name property so it is known in the class.
/// </summary>
public static readonly PropertyData NameProperty = RegisterProperty("Name",
typeof(string), string.Empty);
A registered property can be excluded from serialization if wanted. When the object is deserialized, the default value will be used for the property
in that case.
Default values for reference types
In lots of cases, a default value for reference types is required in the property definitions. However, and you might have noticed this behavior in for
example dependency properties, using an instance as default value can result in unexpected behavior.
Below is an example of a "regular" property registration using a default value for a collection property:
public static readonly PropertyData NameProperty =
RegisterProperty("PersonCollection", typeof(Collection<Person>), new
Collection<Person>());
However, instead of creating a new collection for each new object with this property, only one collection will be created that will be used by all
classes that have this property registered. One solution is to pass null as default value and create the collection in the constructor. A better
solution is to use the override of with the callback parameters: RegisterProperty
public static readonly PropertyData NameProperty =
RegisterProperty("PersonCollection", typeof(Collection<Person>), () => new
Collection<Person>());
This way, every time a new value is needed, the callback will be invoked to create the default value and you will have a true default value for
reference types.
Functionality provided out of the box
The provides a lot of functionality out of the box. A few points I want to mention are: ModelBase
INotifyPropertyChanged
All properties registered using the method automatically take care of change notifications. RegisterProperty
IDataErrorInfo
It is very easy to set field and business errors using the and methods that can be used in the overridable SetFieldError SetBusinessError Validate
and methods. Fields ValidateBusinessRules
IEditableObject
The data object can automatically create an internal backup and restore it, if required, using the interface. IEditableObject
Serialization
As told many times before, using the you can simply save your file to a stream (file on disk, stream in memory, etc.). SavableModelBase,
Keep in mind that this class is not suitable for database communication, there are much better ways to handle this (ORM mappers such as Entity
Framework, NHibernate, LLBLGen Pro, etc.).
Using ModelBase as base for entities
It is possible to use the as base class when using EF or any other OR mapper. ModelBase
There are a few caveats when using the as base class for your entities. One of them is that is always true because the ModelBase IsDirty
properties from the persistence store are set after the constructor. This guide will explain how to work past that problem.
1. Create a class named with the following code: EntityBase
public class EntityBase<T> : ModelBase<T>
where T : class
{
protected EntityBase() { }
protected EntityBase(SerializationInfo info, StreamingContext context)
: base(info, context) { }
internal void ClearDirtyFlag()
{
IsDirty = false;
}
}
2. Derive from instead of so the layer that loads the data can clear the flag. EntityBase ModelBase IsDirty
3. When loading the data from the database and setting the initial values, use this code:
var company = new DTO.Company()
{
Address = domainEntity.Address,
City = domainEntity.City,
CompanyID = domainEntity.CompanyID,
CompanyName = domainEntity.CompanyName,
PostalCode = domainEntity.PostalCode,
PostalCodeAndCity = domainEntity.PostalCodeAndCity
};
company.ClearDirtyFlag();
return company;
Note the call, which is very important to make the property behave correctly. ClearDirtyFlag IsDirty
4. Check the of the model, not the view model when checking whether the model is dirty inside a view model. IsDirty
There is no need to decorated the classes with or attributes with one single exception: when a class contains DataMember DataContract
properties that are defined using a base class, the descending (or inherited) classes must be known. Catel could, in theory, reflect the whole App
to gather these classes, but that would be a major hit on performance. Therefore the inherited classes must be defined manually using Domain
the attribute. KnownType
[Serializable, KnownType(Manager), KnownType(Employee)]
public class Job : ModelBase<Job>
{
// abstract Person class which can be Manager or Employee
public Person Person { get; set; }
}
When adding the service reference, make sure to check the "Reuse types in all referenced assemblies" like shown in the image below:
With the help of the plugin, it is possible to dynamically weave custom methods so no custom code is Catel.Fody GetXmlSchema
required to support WCF and DataContracts
Exception handling
With exception handling in Catel, it is possible to create an exception handling policy and execute code in a safe way without have to check all the
exception types manually.Catel exposes this technique via the IExceptionService.
Setting up the IExceptionService
Executing code using the IExceptionService
Executing an action
Executing a function
Handling exceptions manually
Unregistering exceptions
Buffering
Setting up the IExceptionService
It is important to register an exception in the service and let Catel know how it should be handled. The service handles exceptions in the way they
are added to the IExceptionService.
The example below registers several exceptions and how they should be handled. When a FileNotFoundException occurs, it will show a message
to the user. For any other exception, it will log the exception and show a message that the user should contact the developers.
var exceptionService = GetService<IExceptionService>();
exceptionService.Register<FileNotFoundException>(exception =>
GetService<IMessageService>().Show(exception.Message));
exceptionService.Register<Exception>(exception => {
Log.Error(exception);
GetService<IMessageService>().Show("An unknown exception occurred, please contact the
developers");
});
Executing code using the IExceptionService
The Process method is responsible to keep track of all exceptions which might occur and will handle them if they are registered. If one of your
registered exceptions is thrown by the code, the Process method will handle it and perform the action defined while the registration operation (for
example, by showing a message box).
The Process method comes in two flavors: as action and as function.
Executing an action
var exceptionService = GetService<IExceptionService>();
exceptionService.Process(() => { throw new ArgumentOutOfRangeException(); });
Executing a function
var exceptionService = GetService<IExceptionService>();
var result = exceptionService.Process<int>(() => 1 + 1);
Handling exceptions manually
It is possible to manually handle exceptions using the service. This is useful when you don't want to wrap code in the Process method, but still
want to be able to create a generic exception handling policy.
var exceptionService = GetService<IExceptionService>();
try
{
var value = 150/0;
}
catch (DivideByZeroException exception)
{
exceptionService.HandleException(exception);
}
If the exception can be handled, the registered action will be executed, but your code can safely continue. If the exception (in this case
DivideByZeroException) is not registered, the HandleException method will rethrow the exception.
The IExceptionService checks for type hierarchy. For example, when an exception as type Exception is registered, this handler will
handle all exceptions
Unregistering exceptions
Although it will probably hardly used, it is possible to unregister exceptions again using the code below:
var exceptionService = GetService<IExceptionService>();
exceptionService.Unregister<ArgumentException>();
Buffering
You can want to throttle downthe number of exceptions you receive when a production process goes awry for example. You can do it through the
extension method as shown below : UsingTolerance
var exceptionService = GetService<IExceptionService>();
exceptionService.Register<DivideByZeroException>(exception => { })
.UsingTolerance(9, TimeSpan.FromSeconds(10.0));
Here, the idea is to only receive the 10 exception message.
th
IoC (ServiceLocator and TypeFactory)
Before Catel 2.0, the IoC container used internally was Unity. However, this forced all users to use and configure Unity as the IoC container in
their apps and required the shipping of the libraries as well. Since Catel 2.0, a different technique is used which allows the end-developer to use
the IoC container technique of their choice.
Different components in IoC
Getting components for any object
Replacing the default components
Replacing the default ServiceLocator
Replacing the default TypeFactory
Replacing the default DependencyResolver
Also see:
Introduction to the ServiceLocator
Introduction to the TypeFactory
Introduction to DependencyResolver and DependencyResolverManager
Dependency injection
Ensuring integrity of the ServiceLocator
Synchronization with external containers
Using MEF as primary IoC container
Using Unity as primary IoC container
Setting up the ServiceLocator using configuration
Automatically registering types using attributes
Different components in IoC
There are several different components that are very important for the IoC in Catel:
ServiceLocator
Component that is responsible for the registrations of all types. This is the actual IoC container.
TypeFactory
Component that is responsible to create types. This uses the to retrieve the types which are required to instantiate a IServiceLocator
type.
DependencyResolver
Light-weight implementation of the which does not expose any register methods, but only allows to resolve types. IServiceLocator
Getting components for any object
In every object, it is possible to use the properties to retrieve the instances of each component. This will cause problems when different Default
1.
2.
1.
2.
1.
2.
scoping is used. To always be sure to get the right component for the object you are working with, it is recommended to use the following
extension methods:
public class MyService
{
public void SomeMethod()
{
// If you need to create a type with the current scope type factory
var typeFactory = this.GetTypeFactory();
// If you need to register a type with the current scope service locator
var serviceLocator = this.GetServiceLocator();
// If you need to resolve a type with the current scope and the type is not
injected via dependency injection
var dependencyResolver = this.GetDependencyResolver();
}
}
Replacing the default components
By default, Catel provides very fast and functional implementations of the component interfaces. It is possible though that one needs to use a
different container than the specified ones.
Replacing the default ServiceLocator
Create a custom implementation IServiceLocator
Register the custom implementation:
var serviceLocator = new MyCustomServiceLocator();
var dependencyResolver = new CatelDependencyResolver(serviceLocator);
var typeFactory = new TypeFactory(dependencyResolver);
IoCConfiguration.DefaultServiceLocator = serviceLocator;
IoCConfiguration.DefaultDependencyResolver = dependencyResolver;
IoCConfiguration.DefaultTypeFactory = typeFactory;
Replacing the default TypeFactory
Create a custom implementation ITypeFactory
Register the custom implementation:
var typeFactory = new MyCustomTypeFactory(IoCConfiguration.DefaultDependencyResolver);
IoCConfiguration.DefaultTypeFactory = typeFactory;
Replacing the default DependencyResolver
Create a custom implementation IDependencyResolver
Register the custom implementation:
Note that when any component is replaced, it must be registered with the other instances that are already running. Catel cannot do this
automatically because it is not aware how other (customized) components interact or require registration.
2.
var dependencyResolver = new
CatelDependencyResolver(IoCConfiguration.DefaultServiceLocator);
var typeFactory = new TypeFactory(dependencyResolver);
IoCConfiguration.DefaultDependencyResolver = dependencyResolver;
IoCConfiguration.DefaultTypeFactory = typeFactory;
Introduction to the ServiceLocator
The services as the container inside Catel. ServiceLocator
Internally it uses the as instantiator for the services. TypeFactory
Catel uses it's own ServiceLocator implementing the IServiceLocator to gather all services required by Catel. For example, default services are
the IPleaseWaitService and the IUIVisualizerService. By default, when the first view model is instantiated, Catel registers all default out of the box
services to the ServiceLocator. However, it only does this when the specific services are not already registered. This allows an end-developer to
register his/her own implementations of the services before any view model is instantiated (for example, at application startup).
The ServiceLocator can be instantiated, but Catel instantiates one instance that can be used and shared amongst all objects inside the same
AppDomain. The ServiceLocator can be retrieved by using ServiceLocator.Instance. The ViewModelBase implements GetService which internally
calls ServiceLocator.ResolveType. This way, the end-developer does not have to think about instantiating or retrieving the right ServiceLocator,
but can simply use GetService to retrieve the implementation of a specific registered interface.
Registering a type
Registering an instance of a type
Registering a type via MissingType event
Resolving a type
Instantiation styles
What does instantiation style mean?
Registering a type with instantiation style in Catel
External container instantiation style configuration
Advanced information
Registering a type
Registering a type in the ServiceLocator is very simple and works like any other IoC container:
ServiceLocator.Default.RegisterType<IPleaseWaitService, PleaseWaitService>();
Registering an instance of a type
Catel uses Activator.CreateInstance to create the interface implementations when the object is first resolved. However, sometimes a service
constructor requires parameters or takes a long time to construct. In such cases, it is recommended to create the type manually and register the
instance of the type:
var pleaseWaitService = new PleaseWaitService();
ServiceLocator.Default.RegisterInstance<IPleaseWaitService>(pleaseWaitService);
Registering a type via MissingType event
The ServiceLocator gives the end-developer a last-resort chance to register a type when it is not registered in the ServiceLocator or any of the
external containers. This event is very useful for logging (then the developer in the log knows exactly what type is missing from the IoC container)
or it can be used to determine at runtime in a very late stage what implementation of the service must be used. To register a type via the event,
subscribe to the event and then use the following code:
For more information how types are instantiated and dependency injection, take a look at the TypeFactory documentation
private void OnMissingType(object sender, MissingTypeEventArgs e)
{
if (e.InterfaceType == typeof(IPleaseWaitService))
{
// Register an instance
e.ImplementingInstance = new PleaseWaitService();
// Or a type
e.ImplementingType = typeof(PleaseWaitService);
}
}
If both the and are filled, the will be used. ImplementingInstance ImplementingType ImplementingIntance
Resolving a type
To retrieve the implementation of a service, use the following code:
var pleaseWaitService = ServiceLocator.Default.ResolveType<IPleaseWaitService>();
Instantiation styles
The ServiceLocator in Catel supports instantiation style.
What does instantiation style mean?
Some popular IoC containers support several instantiation styles but they could be summarized into Singleton or Transient. Basically if a type is
registered with singleton instantiation style then when the type will be resolved using the IoC container, it will always return the same instance.
Otherwise, a new instance is created every time the service is resolved from the IoC container.
Example 1: Registering type into Unity Container with singleton instantiation life style
UnityContainer container = UnityContainer();
container.RegisterType<IPleaseWaitService, PleaseWaitService>(new
ContainerControlledLifetimeManager());
Registering a type with instantiation style in Catel
Before Catel 3.3, the ServiceLocator only supported singleton instantiation style, therefore a sequence of a type resolution always has returned
the same instance for the requested type.
Now we added a boolean argument to all type registration method family in order to setup if the instantiation style named singleton. We also care
about to the Catel's user, therefore the default value of this argument is true, avoiding unexpected application behaviors provoked by a transient
instantiation style.
Example 2: Registering type into the ServiceLocator with transient instantiation life style
ServiceLocator.Instance.RegisterTypeIfNotYetRegistered<IRegionNavigationJournal,
RegionNavigationJournal>(false);
External container instantiation style configuration
The ServiceLocator supports synchronization with most of the popular IoC containers, for instance MEF, Unity, Windsor and Ninject. This means
that you are able to choose between those container and use one of them as primary one. It is also possible to use on the ServiceLocator of Catel
alone. Therefore, if you register an external container into the ServiceLocator and register a type into this external container or into the
ServiceLocator, specifying the instantiation style, the instance will be resolved using the right instantiation style.
1.
2.
3.
4.
1.
2.
3.
Example 3: Resolving a transient instance of IRegionNavigationJournal type from Unity container registered via Catel ServiceLocator
UnityContainer container = container();
ServiceLocator.Instance.RegisterExternalContainer(container);
ServiceLocator.Instance.RegisterType<IRegionNavigationJournal,
RegionNavigationJournal>(true, false);
var regionNavigationJournal = container.Resolve<IRegionNavigationJournal>();
Advanced information
The ServiceLocator is a pretty advanced implementation of IoC without the requirements of any reference to, for example, Unity. There are a few
inner workings that are very important that are explained in this section. Internally, this is the workflow that the ServiceLocator use to resolve
types:
Check if an instance is already created so it can be returned
Check if the type is registered in the ServiceLocator itself
Loop all external containers and check whether the type is registered there
If the ServiceLocator nor the external containers have the type registered, the ServiceLocator raises the MissingType event which gives
the developer a last opportunity to either log the missing type or register it via the event
Introduction to the TypeFactory
Dependency injection
Introduction to dependency injection
Using dependency injection in Catel
Disabling dependency injection
Custom initialization
The is responsible for actually creating types inside Catel. It uses the following mechanism: TypeFactory
List all the constructors, order them from most parameters to least parameters
While (constructors available)
try to construct type using injection
If all constructors fail, the will fallback to TypeFactory Activator.CreateInstance()
Dependency injection
The ServiceLocator in Catel supports dependency injection.
Introduction to dependency injection
Some people make dependency injection hard to understand, or maybe they don't understand it themselves. Dependency injection simply means
that instead of hard referencing or instantiating other classes (dependendies), the dependencies are injected into the class via the constructor.
Example 1: bad, instantiates the dependencies itself
public class MyClass
{
private IFirstDependency _firstDependency;
private ISecondDependency _secondDependency;
public MyClass()
{
_firstDependency = new FirstDependency();
_secondDependency = new SecondDependency();
}
}
Be aware when using MEF as primary container. MEF doesn't support type registration, so it only support singleton instantiation style
Example 2: good, retrieves the dependencies via the service locator
public class MyClass
{
private IFirstDependency _firstDependency;
private ISecondDependency _secondDependency;
public MyClass()
{
_firstDependency = ServiceLocator.Instance.ResolveType<IFirstDependency>();
_secondDependency = ServiceLocator.Instance.ResolveType<ISecondDependency>();
}
}
var vm = typeFactory.CreateInstance<MyViewModel>();
In this example, a view model is created with a custom dependency scope. When writing an extension method for the view models, it is
impossible to get to this custom scope:
public static class MyViewModelExtensions
{
public static void DoSomething(this MyViewModel myViewModel)
{
// You can use ServiceLocator.Default here, but that is a *different and
wrong* scope
var additionalDependency =
ServiceLocator.Default.ResolveType<IAdditionalDependency>();
}
}
One option to solve this is to create a property on the view model called or . However, it is the DependencyResolver ServiceLocator not
responsibility of the view model to store this property. In fact, the view model does not know which scoping was used to create itself. The only way
to solve this is to inject the into the view model, but that's not a good practice. IServiceLocator
Below is a rewritten version of the extensions class which uses the : DependencyResolverManager
public static class MyViewModelExtensions
{
public static void DoSomething(this MyViewModel myViewModel)
{
// Get the *right* scope
var dependencyResolverManager = DependencyResolverManager.Default;
var dependencyResolver =
dependencyResolverManager.GetDependencyResolverForInstance(myViewModel);
var additionalDependency =
dependencyResolver.Resolve<IAdditionalDependency>();
}
}
Now you have the actual that was use to create the view model and can easily provide the right logic with the right scoping. IDependencyResolver
The main strategy will be to use the instead of to resolve the types in Catel, starting with version DependencyResolver ServiceLocator
3.8
Managing the dependency resolvers per instance
The can manage dependency resolvers per instance. This way it is possible to retrieve the actual dependency DependencyResolverManager
resolver for a specific object instance.
Registering a dependency resolver for an instance
To register a dependency resolver for an instance, use this code:
var serviceLocator = new ServiceLocator();
var dependencyResolver = new CatelDependencyResolver(serviceLocator);
var myObject = new MySpecialObject();
return base.GetDependencyResolverForType(type);
}
}
Then set the static property: DependencyResolverManager.Default
Note that these registrations are type-specific. You cannot register an interface and all classes deriving from that interface will return the
same . All actual types must be registered separately. DependencyResolver
Note that there is no use to override the as the example, but this keeps it easy to understand DependencyResolverManager
DependencyResolverManager.Default = new CustomizedDependencyResolverManager();
Dependency injection
The ServiceLocator in Catel supports dependency injection.
Introduction to dependency injection
Using dependency injection in Catel
Advanced dependency injection
Disabling dependency injection
Introduction to dependency injection
Some people make dependency injection hard to understand, or maybe they don't understand it themselves. Dependency injection simply means
that instead of hard referencing or instantiating other classes (dependendies), the dependencies are injected into the class via the constructor.
Example 1: bad, instantiates the dependencies itself
public class MyClass
{
private IFirstDependency _firstDependency;
private ISecondDependency _secondDependency;
public MyClass()
{
_firstDependency = new FirstDependency();
_secondDependency = new SecondDependency();
}
}
The advanced dependency injection can be used by using the class. Below is an example on how to create a new type using TypeFactory
This feature is initially written to support dependency injection in combination with nested user controls
advanced dependency injection:
var personViewModel =
TypeFactory.Default.CreateInstanceWithParametersAndAutoCompletion<PersonViewModel>(new
Person());
As you can see it is only required to pass in the objects that are not registered in the IoC container. All other dependencies will be automatically
resolved from the . ServiceLocator
Disabling dependency injection
Maybe you don't want dependency injection because it does not give you what you need or you want a very, very small improvement in
performance. In that case, the dependency injection can be disabled using the code below:
ServiceLocator.Default.SupportedDependencyInjection = false
Ensuring integrity of the ServiceLocator
Starting with Catel 3.6, a very useful feature has been added to the and . This features is called "integrity checker" ServiceLocator TypeFactory
and will ensure you with useful information about type registration paths. This protection mechanism is very useful in complex applications. When
people start building services, sometimes they accidentally inject other services that via injection to other services cause a stack overflow.
Debugging and determining which type is causing the issue can be very time-consuming.To make the example a bit more simple, below are a
few classes which demonstrate a common issue in enterprises.
public class X
{
public X(Y y) { }
}
public class Y
{
public Y(Z z) { }
}
public class Z
{
public Z(X x) { }
}
Note how a round-trip of dependencies is created which will result in a StackOverflowException somewhere in your code.Below is a graphical
example what happens. Note that the dotted line is showing the circular dependency causing the . StackOverflowException
Note that the order of the parameters must be the same as the constructor, otherwise the cannot determine the right TypeFactory
constructor to use
TypeRequestInfo
The first step for the integrity checker is to make sure that it knows what types are being requested from the (which will be ServiceLocator
instantiated by the if required). This class contains all the information about a type being created by the TypeFactory TypeFactory:
Type
Tag (optional, can be used to differentiate different instances of the same type registration)
TypeRequestPath
Now we have detailed information about the types being constructed, it is very important to keep track of the types which are being created by the
. During the construction of a type, the will request the for a type, which will ask the to TypeFactory TypeFactory ServiceLocator TypeFactory
construct the type again.Each time the starts constructing a type (and currently has a ), it will create a new TypeFactory TypeRequestPath
instance of the and add it to the . The diagram below shows how the will evolve. TypeRequestInfo TypeRequestPath TypeRequestPath
Once the will contain a duplicate instance of a , it will become invalid (which means there is a circular type TypeRequestPath TypeRequestInfo
dependency).
Checking the integrity of the type request
To resolve and construct a type, a lot of communication will happen between the and the . TypeFactory ServiceLocator This flow is show in the
diagram below.
Note that this is a very simple example, but normally a type will have several services injected which can have dependencies on their
own as well which can cause a very complex type request path
1.
2.
3.
4.
As you can see, there is a lot of communication between the and . In the example we already saw ServiceLocator TypeFactory TypeRequestPath
how the path will become invalid when it contains a duplicate instance of the . The will then throw a TypeRequestInfo TypeRequestPath CircularD
with all the necessary information to solve the issue: ependencyException
Now you will find the issue in no-time and save yourself a lot of your valuable time!
Synchronization with external containers
Registering external containers
Synchronization between containers
Synchronization from external containers to ServiceLocator
Synchronization from ServiceLocator to external containers
Automatic or manual synchronization
External containers that only support instances
External containers supported
Registering external containers
As explained earlier, the ServiceLocator supports external containers to make sure that the end-developer can use the IoC container of his/her
choice and is not forced by Catel to use any specific IoC implementation. Registering a container is very simple and can be done at any time (but,
must of course be done before service registered in the external container can be used by the ServiceLocator itself):
ServiceLocator.Default.RegisterExternalContainer(myUnityContainer);
Registering an external container makes it possible to resolve types from the ServiceLocator that are originally registered in the external
container. For example, a database repository is registered in Unity, but must be used inside a view model. This is what happens:
End-developer registers ICustomerRepository in a Unity container
End-developer registers the external container in the ServiceLocator of Catel
In a view model, the developer uses GetService<ICustomerRepository>() to retrieve the repository
Internally, the ServiceLocator checks which external container has the type and uses that specific container to return the type
1.
2.
1.
2.
Synchronization between containers
Until now, it all seems very nice. However, there are some aspects that are very hard and which must be taken into account. For example, think of
the following issues:
Instance caching
Which container is the actual owner of a type?
Synchronization from external containers to ServiceLocator
Internally, the ServiceLocator keeps a reference of all registered types in the internal container, all instantiated types and the original container
per type. For example, think of the following situation:
A type is registered in Unity (but the ServiceLocator does not know whether it's a type or instance since Unity makes no difference
between them)
A developer resolves the type in the container
The ServiceLocator checks whether there is already an instance created or requested earlier by the developer. If it already has an instance in the
cache, it simply returns the instance from the cache. If not, it checks which container (it might be itself, but also one of the external containers) is
the owner of the type. A container is considered the owner if the type was first found on that specific container. The ServiceLocator lets the
external container (in this case Unity) instantiate the type. Then the ServiceLocator stores the instance in the instance cache so it can be easily
resolved in the next call.
Synchronization from ServiceLocator to external containers
Great, now think of this situation:
A type is registered in the ServiceLocator
The type must be injected into a constructor using Unity
This is a bit harder because the type is not registered in Unity, but it is used by Unity. Luckily, the ServiceLocator is able to automatically register
types in all external containers as soon as a type is registered or resolved. This way, when a type is registered in the ServiceLocator, it
automatically calls ExternalContainer.RegisterType (or (ExternalContainer.RegisterInstance in case of an instance) for all external containers.
This way, it is possible to register a type in the ServiceLocator, but still be able to use the powerful dependency injection of Unity.
Automatic or manual synchronization
By default, the ServiceLocator automatically keeps all the IoC containers in sync so the end-developer should not have any knowledge of the
inner workings of the ServiceLocator. Sometimes, a developer wants to have more control and needs to disable this automatic behavior. This is
possible using the following property:
ServiceLocator.Instance.AutomaticallyKeepContainersSynchronized = false;
Then it is still possible to manually synchronize the containers using the following methods:
ExportInstancesToExternalContainers => exports instances only
ExportToExternalContainers => exports instances and registered types
External containers that only support instances
There are external containers, such as MEF, that only support the registration of instances, not types. If such an external container is registered,
all the types registered in the ServiceLocator are instantiated and registered as an instance in the external container. If this behavior is not
acceptable, disable the automatic synchronization and handle the synchronization manually via the AutomaticallyKeepContainersSynchronized
property.
External containers supported
Currently, the following external IoC containers are supported:
Castle Windsor
MEF
Ninject
Unity
If there is need for other containers, just let us know!
Using MEF as primary IoC container
If you are going for Catel, we always recommend to use the ServiceLocator of Catel as primary IoC container. However, if you want to use MEF
as the primary IoC container, follow the instructions below.
Registering the container in Catel
It is required to register the external container in the ServiceLocator of Catel. This is required because all the items that are registered by Catel
itself are known in the MEF container. Catel will automatically register all types in the MEF container when the container is registered. To register
the container, simple use this call when the container is created:
ServiceLocator.Instance.RegisterExternalContainer(myMefContainer);
At this point, all the services registered by Catel are now registered in the MEF container.
Importing properties
To import services on a class, decorate the properties
[Import]
public IPleaseWaitService PleaseWaitServiceViaMEF { get; set; }
The code above imports the IPleaseWaitService (which is internally registered by Catel) via MEF.
Satisfying imports
After all imports are added, it is important to satisfy the imports using MEF. The imports can be satisfied via the following code:
myMefContainer.SatisfyImportsOnce(this);
Using Unity as primary IoC container
If you are going for Catel, we always recommend to use the ServiceLocator of Catel as primary IoC container. However, if you want to use Unity
as the primary IoC container, follow the instructions below.
Registering the container in Catel
It is required to register the external container in the ServiceLocator of Catel. This is required because all the items that are registered by Catel
itself are known in the Unity container. Catel will automatically register all types in the Unity container when the container is registered.To register
the container, simple use this call when the container is created:
ServiceLocator.Instance.RegisterExternalContainer(myUnityContainer);
At this point, all the services registered by Catel are now registered in the Unity container.
This is no longer the recommended solution. Please see instead. how to customize the ServiceLocator
This is not a full starting guide to MEF, if you are looking for documentation about MEF, continue searching
This is no longer the recommended solution. Please see instead. how to customize the ServiceLocator
This is not a full starting guide to Unity, if you are looking for documentation about Unity, continue searching
Resolving properties
In Unity, it is required to resolve properties like this:
PleaseWaitService = myUnityContainer.Resolve<IPleaseWaitService>();
The code above imports the IPleaseWaitService (which is internally registered by Catel) via Unity.
Setting up the ServiceLocator using configuration
The in Catel can be set up from configuration file. ServiceLocator
Importing IoC configuration section
Configuring a service locator from the default configuration
Configuring a service locator from a named configuration
Importing IoC configuration section
The first step to setup the service locator from the configuration file is import the custom section type from Catel.IoC.IoCConfigurationSection Cat
. The following example shows how to import this configuration section and make it available for the configuration file as : el.Core ioc
<configuration>
<configSections>
<sectionGroup name="catel">
<section name="ioc" type="Catel.IoC.IoCConfigurationSection, Catel.Core" />
</sectionGroup>
</configSections>
...
</configuration>
Configuring a service locator from the default configuration
It's possible add more than one service locator configuration to the configuration file but you must specify an unique name. If a name of a service
locator configuration is not specified then the name is assigned. By default such configuration supports dependency injection. default
In the example above we also create a section group named catel to group all Catel related configuration sections.
<configuration>
<configSections>
<sectionGroup name="catel">
<section name="ioc" type="Catel.IoC.IoCConfigurationSection, Catel.Core" />
</sectionGroup>
</configSections>
<catel>
<ioc>
<serviceLocatorConfigurations>
<serviceLocatorConfiguration [name="default"]
[supportDependencyInjection="true"]>
<register interfaceType="Catel.MVVM.Services.IUIVisualizerService"
implementationType="Catel.MVVM.Services.UIVisualizerService" />
<register interfaceType="Catel.MVVM.Services.IProcessService"
implementationType="Catel.MVVM.Services.ProcessService" />
</serviceLocatorConfiguration>
</serviceLocatorConfigurations>
</ioc>
</catel>
</configuration>
To configure a service locator from the default service locator configuration use the following code:
MessageBase
The MessageMediator is a very powerful class to send messages to other objects inside an application. However, it can sometimes by
cumbersome to register and create messages. Therefore the MessageBase class is a very nice convenience class to create messages and allow
easier registration.
The MessageBase provides the following additional functionality out of the box:
Send messages with data without instantiating a message
Register message handlers
Unregister message handlers
Creating messages based on the MessageBase
It is very easy to create a new message. The message below is a message that contains a string and this little class provides lots of capabilities.
public class DemoMessage : MessageBase<DemoMessage, string>
{
public DemoMessage() { }
public DemoMessage(string content)
: base(content) { }
}
Sending messages
A user can send a message by using the following code:
DemoMessage.SendWith("hello world");
Registering to messages
A class that is interested in message can register to a message using the Register method:
DemoMessage.Register(this, OnDemoMessage);
Unregistering from messages
DemoMessage.Unregister(this, OnDemoMessage);
Instantiating a message with data
The MessageBase class can also instantiate messages by using the With method:
var message = DemoMessage.With("hello world");
Message mediator
Catel allows sending messages to unknown targets by implementing the mediator pattern. The mediator is assured memory leak free, and can be
used safely in any .NET environment (even ASP.NET). Below are a few usage examples of the MessageMediator class.
Registering to a message
To register a handler for a specific message type, in this case a string, use the following code:
var mediator = ServiceLocator.Instance.ResolveType<IMessageMediator>();
mediator.Register<string>(this, OnMessage);
Sending out a message
Note that the message needs an empty constructor
To send a message to all recipients, use the following code:
var mediator = ServiceLocator.Instance.ResolveType<IMessageMediator>();
mediator.SendMessage<string>("message");
Sending out a message with a tag
Sometimes, you want to send messages only based on a tag. For example, you want to let other view models know that you just added a person.
All recipients that registered to the string message type with the Person tag will receive the message:
var mediator = ServiceLocator.Instance.ResolveType<IMessageMediator>();
mediator.SendMessage<string>("Person added", "Person");
Messaging via attributes
The message mediator is a great way to communicate between instances in an application. It does however require to manually subscribe to and
unsubscribe from classes. This issue can be bypassed using the attribute based approach. This is an alternative for registering a method in the
message mediator and not be obliged to use Register<T> method.
Subscribing and unsubscribing
When attributes are using inside a class, it is required to call the MessageMediatorHelper.SubscripeRecipient. To unsubscribe an object, it is
required to call MessageMediatorHelper.UnsubscribeRecipient.
There are two options to decorate methods with the attribute. Either with or without tag.
Subscribing without a tag
In this case, the mediator will send the message to all the methods that has subscribe using the attribute to receive the message and not one
especially. The code below broadcasts a message without any tag. This is just regular behavior of the message mediator.
/// <summary>
/// Method to invoke when the command is executed.
/// </summary>
private void OnCmdExecute()
{
var mediator = GetService<IMessageMediator>();
mediator.SendMessage("Test Value");
}
If a class, for example a view model, is interested in these messages, the only thing that needs to be done is to decorate a method with the
MessageRecipient attribute as shown below:
Note that the ViewModelBase class already handles the subscriptions using attributes out of the box
/// <summary>
/// Shows the message.
/// </summary>
/// <param name="value">The value.</param>
[MessageRecipient]
private void ShowMessage(string value)
{
var messageService = GetService<IMessageService>();
messageService.Show(value);
}
Subscribing with a tag
A tag can be used to specify some sort of grouping for messages. The MessageRecipient attribute also supports this as shown in the code
below. First lets take a look how to send a message and specify a tag.
/// <summary>
/// Method to invoke when the command is executed.
/// </summary>
private void OnCmdExecute()
{
var mediator = GetService<IMessageMediator>();
mediator.SendMessage("Test Value", "myTag");
}
The message is now sent with the tag. The attribute has to be used as shown below:
/// <summary>
/// Shows the message.
/// </summary>
/// <param name="value">The value.</param>
[MessageRecipient(Tag = "myTag")]
private void ShowMessage(string value)
{
var messageService = GetService<IMessageService>();
messageService.Show(value);
}
Preventing memory leaks
Memory leaks are a very stubborn issue inside the .NET world. Every subscription to events can result in a memory leak if not unsubscribed
correctly. Catel provides several helper classes to prevent memory leaks in applications.
Change notification wrapper
Weak events
Change notification wrapper
Subscribing to change notifications of objects mostly results in large statements such as the one below:
1.
2.
var itemAsPropertyChanged = obj as INotifyPropertyChanged;
if (itemAsPropertyChanged != null)
{
itemAsPropertyChanged.PropertyChanged += OnPropertyChanged;
}
However, using this code one must be aware that if not unsubscribed, there might be a potential memory leak here. In Catel, there is a solution for
such cases that can raise change notifications using weak events called the ChangeNotificationWrapper. It allows the subscription of both the
INotifyPropertyChanged and INotifyCollectionChanged interfaces.
Subscribing to events of an observable object
Using the code below, one can subscribe to the PropertyChanged event of an object:
var wrapper = new ChangeNotificationWrapper(obj);
wrapper.PropertyChanged += OnPropertyChanged;
Subscribing to events of an observable collection
Using the code below, one can subscribe to the CollectionChanged event of an object:
var wrapper = new ChangeNotificationWrapper(observableCollection);
wrapper.CollectionChanged += OnCollectionChanged;
Advanced scenario with observable collections
Sometimes it is required to watch both changes inside a collection, but also the items inside a collection. For example, there is a list of customers
and you are also interested in changes of customers inside a collection. This is fully supported by the ChangeNotificationWrapper using the code
below:
var wrapper = new ChangeNotificationWrapper(observableCustomerCollection);
wrapper.CollectionChanged += OnCollectionChanged;
wrapper.CollectionItemPropertyChanged += OnCollectionItemPropertyChanged;
All subscriptions are automatically managed by the ChangeNotificationWrapper when items are added or removed from the collection.
Unsubscribing from events
When you are no longer interested in events from the source object, there are two options:
Just leave them coming, as soon as the objects are no longer used, they will be garbage collected
Unsubscribe using the following code:
wrapper.UnsubscribeFromAllEvents();
Note that it is not required to check whether the object implements INotifyPropertyChanged, the wrapper does it automatically
Note that it is not required to check whether the object implements INotifyCollectionChanged, the wrapper does it automatically
1.
2.
Weak events
You have probably heard about weak events before. This documentation is not about the issue of the cause of weak events, there are lots of
articles about that. This documentation writes about the solution, which is the WeakEventListener. Shortly said, when you do this in every class
(just for the sake of explaining the problem, dont start thinking this code has no business value):
var log = Log.Instance;
log.LogReceived += OnLogReceived;
As you can see, the log is a singleton, so there is only one living instance of the Log class. It will probably live as long as the app itself. Now you
might be thinking: whats wrong with this code? Nothing, until the app starts growing and growing and your users start complaining about memory
issues.
What happens here is that you subscribe to the LogReceived event of the Log class. This subscription contains 2 things:
What class do I need to call (null for static, otherwise the instance of the class)
What method do I need to call
So, in fact now the Log class knows about the instance of the class that just subscribed to it and holds a reference to it (how else can it deliver the
event, if it doesnt know the address). Thus, the classes that subscribe to the Log and that do no unsubscribe will never be collected by the
garbage collection.
I truly hope you understand the issue. If not, I recommend to read , it dives into the issue a bit better. this excellent article
Open instance delegates
The key feature behind this implementation of the weak event pattern is open instance delegates. You are probably wondering: what the hell are
open instance delegates? Well, good question, and I will try to explain it. An open instance delegate is just as a regular delegate, it points to the
method of a specific class, but the biggest difference is that it does not bind to a specific instance. This means that it can be described as: I know
you live on that street (method), but I have not clue in which city (instance) that is. The instance can be specified later. The delegate for a regular
event handler looks like this:
public delegate void OpenInstanceHandler(TTarget @this, object sender, TEventArgs e);
The @this is nothing special, it allows us to use the this keyword so everyone knows that the target should be passed there. As you can see, it
contains 3 parameters. The first one is the target (the city), the second and third parameters are the parameters of the regular event handler.
Weak references
The weak event listener creates an open instance delegate and stores both the source and target in a WeakReference class. As soon as one of
these references are no longer valid, the class is unbound. The good side of this approach is that this weak event listener does not leak when the
event never fires.
What does it support
The following use cases are supported:
Instance source (event) and instance target (handler)
Static source (event) and instance target (handler)
Instance source (event) and static target (handler)
So, actually it handles everything that can cause a memory leak via event subscriptions!
What does it not support and what are the downsides
This weak event listener follows the rules of the .NET framework. So, it cannot subscribe to private events. If you want private events, do your
own hacking (the source is available, you only have to change the DefaultEventBindingFlags at the top of the class).
There are a few downsides about using a weak event listeners in general:
Its notation is ugly, the original .NET way looks way better
You have to name the event by string, that sucks (if you know a better way, contact me!)
It can only handle events with a handler of EventHandler<TEventArgs>
You become a lazy developer not caring about subscriptions
How to use
There are 4 categories of event subscriptions, all described below.
Instance to instance
This is the situation where an instance target subscribes to an instance event. The events are unbound as soon as either the target or source are
collected.
var source = new EventSource();
var listener = new EventListener();
var weakEventListener = WeakEventListener<EventListener, EventSource,
EventArgs>.SubscribeToWeakEvent(listener, source, "PublicEvent",
listener.OnPublicEvent);
Instance to static
This is the situation where a static target subscribes to an instance event. The events are unbound as soon as the source is collected.
var source = new EventSource();
var weakEventListener = WeakEventListener<EventListener, EventSource,
EventArgs>.SubscribeToWeakEvent(null, source, "PublicEvent",
EventListener.OnEventStaticHandler);
Static to instance
This is the situation where an instance target subscribes to a static event. The events are unbound as soon as the target is collected.
var listener = new EventListener();
var weakEventListener = WeakEventListener<EventListener, EventSource,
EventArgs>.SubscribeToWeakEvent(listener, null, "StaticEvent",
listener.OnPublicEvent);
Static to static
This is not supported because you shouldnt be using a weak event listener here. Static events with static event handlers simply cannot cause
memory leaks because both the source and the target have no instance. However, it might be possible that you subscribe to an event too many
times and the event fires too many times. But again, no memory issues here.
Reflection
Internally, Catel uses lots of reflection to implement some of its behavior. And why not make all these excellent reflection classes public?
Getting loaded assemblies
In WPF, it is very simple to get all the loaded assemblies. In Silverlight, it already gets a bit harder. Silverlight in combination with modules that
are separately loaded are horrible to use via reflection. In Catel, it is possible to register xap files to the AssemblyHelper class:
AssemblyHelper.RegisterAssembliesFromXap(xapStream);
Catel unpacks the xap (which is basically just a zip file) and adds all the assemblies to an internal list of assemblies. This way, the
AssemblyHelper.GetLoadedAssemblies method actually returns all the loaded assemblies, also the ones in dynamically loaded xaps that are not
available by default.
Getting types in Silverlight
Sometimes you know what type to get and what assembly it is living in. However, you don't want to be version-dependent by specifying the fully
qualified assembly name. Using the TypeHelper.GetType method, it is possible to get a type by only the assembly name (say Catel.Silverlight)
and the type name (say Catel.Data.ObservableObject).
var type = PropertyHelper.GetType("Catel.Data.ObservableObject", "Catel.Silverlight");
Setting or getting properties of objects
In lots of cases, you need to possibility to set or get properties of an object via reflection. This behavior is implemented in the PropertyHelper
class. Below are a few examples.
Check if a property is available on an object
PropertyHelper.IsPropertyAvailable(person, "FirstName");
Getting a property value
PropertyHelper.GetValue(person, "FirstName");
or
string firstName;
PropertyHelper.TryGetValue(person, "FirstName", out firstName);
Setting a property value
PropertyHelper.SetValue(person, "FirstName", "Geert");
or
PropertyHelper.TrySetValue(person, "FirstName", "Geert");
Serialization
Introduction to serialization
Specifying what gets serialized
Customizing serialization
Introduction to serialization
Serialization modes
Serializing a model
Warming up serialization
Warming up specific types
The serialization engine has been completely renewed for Catel 3.7. Make sure that you read about these new features.
Warming up automatically
Warming up using multiple threads
Backwards compatibility for binary serialization
Serialization modes
Depending on the target framework, several options are available as serialization modes:
Serialization mode WPF Silverlight Windows Phone WinRT
Binary
XML
Serializing a model
The code below shows how to save an object (which can, of course, be a complex graph of nested objects):
var myObject = new MyObject();
myObject.Save(@"C:\myobject.dob");
Looks too easy, but this really is the only thing you need to do. You can specify the serialization mode in the several available overloads of theSa
method. ve
Loading is as easy as saving, as you can see in the following code:
var myObject = MyObject.Load(@"C:\myobject.dob");
Warming up serialization
The first time a serializerneeds to serialize an object, it needs to perform some reflection to gather all the required information. This can have a
negative impact on performance for the end-user during serialization. This load cannot be prevented, but it is possible to warmup the serializer at
any time when it is convenient (for example, during startup of the application).
Warming up specific types
This code will warm up all the specified types:
var typesToWarmup = new type[] { typeof(Settings) };
[ExcludeFromSerialization]
public string CatelProperty
{
get { return GetValue<string>(CatelPropertyProperty); }
set { SetValue(CatelPropertyProperty, value); }
}
public static readonly PropertyData CatelPropertyProperty =
RegisterProperty("CatelProperty", typeof(string), null);
}
Note that private members can only be serialized in .NET and not in Silverlight, Windows Phone or the Windows Runtime
Member name Gets serialized
_fieldValue
RegularProperty
CatelProperty
Implementing a custom ISerializationManager
Internally Catel uses a default implementation of the to determine what members of a type should be serialized. It is ISerializationManager
possible to customize this behavior by overriding a single method or by creating a brand new type. Below is an example which always excludesP
properties and fields from serialization. assword
public class SafeSerializationManager : SerializationManager
{
public override HashSet<string> GetFieldsToSerialize(Type type)
{
var fieldsList = new List<string>(base.GetFieldsToSerialize(type));
for (int i = 0; i < fieldsList.Count; i++)
{
if (string.Equals(fieldsList[i], "_password"))
{
fieldsList.RemoveAt(i--);
}
}
return new HashSet<string>(fieldsList);
}
public override HashSet<string> GetPropertiesToSerialize(Type type)
{
var propertiesList = new List<string>(base.GetPropertiesToSerialize(type));
for (int i = 0; i < propertiesList.Count; i++)
{
if (string.Equals(propertiesList[i], "Password"))
{
propertiesList.RemoveAt(i--);
}
}
return new HashSet<string>(propertiesList);
}
}
Don't forget to register it in the as well: ServiceLocator
var serviceLocator = ServiceLocator.Default;
serviceLocator.RegisterType<ISerializationManager, SafeSerializationManager>();
Customizing serialization
The serialization engine explained
Customizing serialization
Customizing the serialization for specific models
Creating the modifier
Registering the modifier
Customizing the serialization engines
The serialization engine explained
All classes deriving from use the serialization engine of Catel to serialize itself in a whole or as a subset of properties. Below is a ModelBase
schema which sheds some light on the architecture.
The now contains all the serialization and deserialization logic. The advantage is that this logic is no longer contained by the SerializerBase Model
itself which makes the class much simpler to understand and maintain. Now the contains all the heavy lifting, the deriving Base SerializerBase
classes ( andBinarySerializer) only have to implement a few methods. XmlSerializer
The serialization process works as shown in the diagram below:
Customizing serialization
Customizing the serialization for specific models
The documentation engine has been completely rewritten for Catel 3.7. This documentation only applies to Catel 3.7 and higher.
Workflow 1 represents the serialization. Workflow 2 represents the deserialization.
Catel has a default behavior for what gets serialized. It can be tweaked by including / excluding fields and properties by using the IncludeInSeriali
and attributes. But sometimes one needs more specific customization of the serialization for a specific type. This zation ExcludeFromSerialization
customization is possible via the . ISerializerModifier
Creating the modifier
To customize the serialization of a specific model type, one needs to implement the interface. The example belows shows how ISerializerModifier
to encrypt the property on the model class. Password Person
public class PersonSerializerModifier : SerializerModifierBase<Person>
{
public override void SerializeMember(ISerializationContext context, MemberValue
memberValue)
{
if (string.Equals(property.Name, "Password"))
{
memberValue.Value = EncryptionHelper.Encrypt(memberValue.Value);
}
}
But some times the scenario is not quite simple, then you need to use the Monitor class in order to synchronize cross method operations. Here is
an example:
private readonly object _syncObj = new object();
public void DoTheWork()
{
StartTheWork();
object result = EndTheWork();
}
private void StartTheWork()
{
Monitor.Enter(_syncObj);
try
{
// Access to the thread-sensitive resources here.
}
catch(Exception)
{
Monitor.Exit(_syncObj);
throw;
}
}
private object EndTheWork()
{
try
{
// Access to the thread-sensitive resources here.
return new object();
}
finally
{
Monitor.Exit(_syncObj);
}
}
To combine the power of the simplicity of the lock statement syntax and the flexibility of the Monitor class, Catel introduces the
SynchronizationContext class, allowing you to write the code like this.
private readonly List<IValidator> _validators = new List<IValidator>();
private readonly SynchronizationContext _synchronizationContext = new
SynchronizationContext();
public bool Contains(IValidator validator)
{
Argument.IsNotNull("validator", validator);
return _synchronizationContext.Execute(() => _validators.Contains(validator));
}
public void Remove(IValidator validator)
{
Argument.IsNotNull("validator", validator);
_synchronizationContext.Execute(() => _validators.Remove(validator));
}
public void BeforeValidation(object instance, List<IFieldValidationResult>
previousFieldValidationResults, List<IBusinessRuleValidationResult>
previousBusinessRuleValidationResults)
{
_synchronizationContext.Acquire();
try
{
foreach (IValidator validator in _validators)
{
validator.BeforeValidation(instance, previousFieldValidationResults,
previousBusinessRuleValidationResults);
}
}
catch (Exception)
{
_synchronizationContext.Release();
throw;
}
}
public void AfterValidateBusinessRules(object instance,
List<IBusinessRuleValidationResult> validationResults)
{
try
{
foreach (IValidator validator in _validators)
{
validator.AfterValidateBusinessRules(instance, validationResults);
}
}
catch (Exception)
{
_synchronizationContext.Release();
throw;
}
}
1.
2.
3.
4.
Acquiring a lock
To acquire a lock, only a call to Acquire is required:
_synchronizationContext.Acquire();
Releasing a lock
To release a lock, only a call to Release is required:
_synchronizationContext.Release();
Automatic locking of a method
It is also possible to automatically lock and release a method call. This can be accomplished using the Execute method.
_synchronizationContext.Execute(() => ThreadSafeCodeExecution());
Validation
Validation is very important for data objects. Therefore, the ModelBase supports all kinds of different validation:
Internal validation via the ValidateFields and ValidateBusinessRules methods
Validation via data annotations (attributes)
External validators using the IValidatorProvider and IValidator interfaces
The validation results are cached and only executed when a property changes (the object becomes dirty) or when the validation is forced.
Validation via validate methods
Validation via data annotations
Validation via special model validators
Validation via IValidator
Using the validation context
Getting a summary of validation results
Deferring validation
Different types of validation
There are two different types of validation in Catel, namely warnings and errors. There are also two flavors of validations, namely field validation
and business rule validation.
Order of execution of events
The order of execution is very important if you want to perform very advanced validation (such as translating validations at the end of each
validation sequence).
IValidator.BeforeValidation
OnValidation (raises Validating event)
if not already validated
IValidator.BeforeValidateFields
SynchronizationContext also allow you create asynchronous locking request, that could be useful in Silverlight Application where the
action of lock the main thread is not allowed.
The ViewModelBase derives from ModelBase, thus all information here also applies to the ViewModelBase
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
OnValidatingFields (raises the ValidatingFields event)
IValidator.ValidateFields
ValidateFields
OnValidatedFields (raises the ValidatedFields event)
IValidator.AfterValidateFields
IValidator.BeforeValidateBusinessRules
OnValidatingBusinessRules (raises the ValidatingBusinessRules event)
IValidator.ValidateBusinessRules
ValidateBusinessRules
OnValidatedBusinessRules (raises the ValidatedBusinessRules event)
IValidator.AfterValidateBusinessRules
end if not already validated
OnValidated (raises the Validated event)
IValidator.AfterValidation
There are lots of events, and it may seem complex and confusing at first sight. However, all these events give developers the opportunity to hook
into the validation sequence at any time.
Validation via validate methods
The easiest way to implement validation is to override the ValidateFields and ValidateBusinessRules methods. Below is an example of an
implementation of the ValidateFields method:
protected override void ValidateFields(List<IFieldValidationResult> validationResults)
{
if (string.IsNullOrEmpty(FirstName))
{
validationResults.Add(FieldValidationResult.CreateError(FirstNameProperty,
"First name is required"));
}
if (string.IsNullOrEmpty(LastName))
{
validationResults.Add(FieldValidationResult.CreateError(LastNameProperty,
"Last name is required"));
}
if (Gender == Gender.Unknown)
{
validationResults.Add(FieldValidationResult.CreateError(GenderProperty,
"Gender cannot be unknown"));
}
}
Validation via data annotations
Data annotations are validation when the specific property is set. For example, when a property FirstName is set, all the data annotations on the
FirstName property are validated.
Decorating properties with data annotations
Decorating properties is very simple. For example, to make a property mandatory, use the following definition (note the Required attribute):
The ViewModelBase derives from ModelBase, thus all information here also applies to the ViewModelBase
The ViewModelBase derives from ModelBase, thus all information here also applies to the ViewModelBase
/// <summary>
/// Gets or sets the middle name.
/// </summary>
[Required]
public string MiddleName
{
get { return GetValue<string>(MiddleNameProperty); }
set { SetValue(MiddleNameProperty, value); }
}
/// <summary>
/// Register the property so it is known in the class.
/// </summary>
public readonly PropertyData MiddleNameProperty = RegisterProperty("MiddleName",
typeof(string), string.Empty);
For more information about data annotations, read the official MSDN documentation.
Validation via special model validators
By default, Catel registers the as the . This way the and all the classes that derive from it AttributeValidatorProvider IValidatorProvider ModelBase
can easily add a custom validator by using the . ValidateModelAttribute
Implementing the validator
Implementing the validator
The first thing that needs to be done is to write a custom implementation of the interface. You can either implement all the members IValidator
yourself or derive from as is shown below: ValidatorBase
Note that it is still possible to register a custom IValidatorProvider to customize this behavior. It is even possible to set the Validator prop
erty of the ModelBase on a specific instance of a model
public class PersonValidator : ValidatorBase<PersonModel>
{
public override void ValidateFields(PersonModel instance,
List<IFieldValidationResult> validationResults)
{
if (string.IsNullOrWhiteSpace(instance.FirstName))
{
validationResults.Add(FieldValidationResult.CreateError(PersonModel.FirstNameProperty,
"First name is required"));
}
if (string.IsNullOrWhiteSpace(instance.LastName))
{
validationResults.Add(FieldValidationResult.CreateError(PersonModel.FirstNameProperty,
"First name is required"));
}
}
<catel:BooleanToVisibilityConverter>
<catel:BooleanToVisibilityConverter.Link>
<code:NullToBoolConverter Link="{StaticResource NullableValueConverter}" />
</catel:BooleanToVisibilityConverter.Link>
</catel:BooleanToVisibilityConverter>
Available converters
Name Description
BooleanToCollapsingVisibilityConverter Convert from bool to and back. Visibility
BooleanToHidingVisibilityConverter Convert from bool to and back. Visibility
BooleanToGrayscaleConverter Converts a boolean to a grayscale saturation value. If the input is
false, this converter will return 0, otherwise 1.
BooleanToOppositeBooleanConverter Convert a boolean to it's inverted value.
BooleanToTextConverter Converts a boolean value to text, for example "yes" and "no", or "x"
and " ".
ColorToBrushConverter Converts a color value to a brush and vice versa.
ContainsItemsConverter Convert the count of a collection to true or false, depending on
whether the collection contains items.
EmptyStringToCollapsingVisibilityConverter Converts a string to . If the string is empty, it will return Visibility Visibili
. ty.Collapsed
EmptyStringToHidingVisibilityConverter Converts a string to Visibility. If the string is empty, it will return Visibili
ty.Hidden.
IntToStringConverter Converts integer to string and back.
IsSelectedConverter Converts a selected value to either true of false.
IsSelectedValueConverter Converts a selected value to either true of false.
MethodToValueConverter Converts the result of a method to a value. This makes it possible to
bind to a method.
MultiplyConverter Calculates the product of given value and factor in parameter.
NullableValueConverter Converts a value to a representive value for nullable.
ReferenceToBooleanConverter Converts a reference to a boolean. If the reference is , it will return null
. false
ReferenceToCollapsingVisibilityConverter Converts a reference to . If the reference is , it will return Visibility null
. Visibility.Collapsed
Note that the behavior of most converters can be inverted by using the ConverterParameter
ReferenceToHidingVisibilityConverter Converts a reference to . If the reference is , it will return Visibility null
. Visibility.Hidden
ShortDateFormattingConverter Converts a date to a short date and back.
StringToIntConverter Converts string to an integer and back.
ViewModelToViewConverter Converts a view model to a view. Great way to locate a view based
on a view model inside xaml.
Designers
Lots of developers are using designers such as the built-in designer in Visual Studio 2010 or Expression Blend to design their xaml based
applications. Although you should , we strive to fully support all designers. use designers with lots of care
Since Catel 1.3, it is possible to create design-time versions of a view model. This way, you can preview the or impleme UserControl DataWindow
ntations using example data. Since Silverlight does not support defining types in xaml, the way that the design time works is a bit different.
WPF
Silverlight & Windows Phone
WPF
To create design-time support for a data window, use the following steps:
1.Create a design time view model. Normally, this can easily be achieved by deriving a new class from the actual view-model and inject the
model. Below is an example of a design time version of a person view model:
/// <summary>
/// Design time version of the <see cref="PersonViewModel"/>.
/// </summary>
public class DesignPersonViewModel : PersonViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="DesignPersonViewModel"/> class.
/// </summary>
public DesignPersonViewModel()
: base(new Person { FirstName = "Geert", MiddleName = "van", LastName =
"Horrik", Gender = Gender.Male })
{
}
}
2.Define the type of the design time view model.
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel}"
If you want it to actually demo data (instead of allowing to configure bindings), use : show IsDesignTimeCreatable
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel,
IsDesignTimeCreatable=True}"
Full DataWindow declaration:
<catel:DataWindow x:Class="Catel.Examples.PersonApplication.UI.Windows.PersonWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:catel="http://catel.codeplex.com"
xmlns:ViewModels="clr-namespace:Catel.Examples.PersonApplication.ViewModels"
xmlns:Converters="clr-namespace:Catel.Examples.PersonApplication.Data.Converters"
xmlns:Models="clr-namespace:Catel.Examples.Models;assembly=Catel.Examples.Models"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel,
IsDesignTimeCreatable=True}">
3. Example of design time data support:
Silverlight & Windows Phone
To create design-time support for a data window, use the following steps:
1.Create a design time view model. Normally, this can easily be achieved by deriving a new class from the actual view-model and inject the
model. Below is an example of a design time version of a person view model:
Although the is not available in Windows Phone, it still shows how to add design time support DataWindow
/// <summary>
/// Design time version of the <see cref="PersonViewModel"/>.
/// </summary>
public class DesignPersonViewModel : PersonViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="DesignPersonViewModel"/> class.
/// </summary>
public DesignPersonViewModel()
: base(new Person { FirstName = "Geert", MiddleName = "van", LastName =
"Horrik", Gender = Gender.Male })
{
}
}
2.Define the type of the design time view model.
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel}"
If you want it to actually demo data (instead of allowing to configure bindings), use : show IsDesignTimeCreatable
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel,
IsDesignTimeCreatable=True}"
Full DataWindow declaration:
<catel:DataWindow x:Class="Catel.Examples.Silverlight.UI.Windows.PersonWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:ViewModels="clr-namespace:Catel.Examples.PersonApplication.ViewModels"
xmlns:Converters="clr-namespace:Catel.Examples.Silverlight.Data.Converters"
xmlns:ExampleWindows="clr-namespace:Catel.Examples.Silverlight.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance ViewModels:DesignPersonViewModel,
IsDesignTimeCreatable=True}">
3.Example of design time data support:
Handling application initialization parameters
Sometimes you need enable or disable some application options as the outcome / result of some initialization parameters.Typically this work is
done at the application main entry point or in a notification such as start event receiving this arguments.But its pretty dirt do all this work at the
application start. May that some application modules are actually unloaded and you dont have actually a way to disable such option or the same
argument have a different meaning cross modules.
The fact is that with Catel, you can delay the processing of such initialization parameters using the and make the right StartUpInfoProvider
interpretation of any parameter at any application point.
Using the StartUpInfoProvider
Now Catel comes with . This class provides the access to the initialization parameters of the application. Basically if you are in StartUpInfoProvider
building a NET application you can access to the command line arguments array or if you are building a Silverlight application you can access to
the initialization parameter dictionary.
The interface of this service is defined below:
/// <summary>
/// The IStartUpInfoProvider interface.
/// </summary>
public interface IStartUpInfoProvider
{
#if SILVERLIGHT
/// <summary>
/// Gets the silverlight application initialization parameters.
/// </summary>
IDictionary<string, string> InitParms { get; }
#endif
#if NET
/// <summary>
/// Gets the application command line argument.
/// </summary>
string[] Arguments { get; }
#endif
}
Advantages of the StartUpInfoProvider
Think that you want to test or debug an application that require analyses the initialization parameters to work. Typically you have options to modify
We are evaluating the way to automatically map the command line array into a meaning full strong typed options and switches map or
dictionary to direct access to command line options
this argument in a configuration windows of the visual studio or in the test page of the Silverlight application.
By default, there no easy way to mock or fake the initialization parameters for an application. Think about it:
Process.GetCurrentProcess().StartInfo.Arguments
or
Application.Current.Host.InitParams
Such thing are actually unmockable.But now, thanks to Catel, you can do it registering fakes or mock instances into the service locator. Just like
this:
var startUpInfoProviderMock = new Moq<IStartUpInfoProvider>();
....
ServiceLocator.Default.RegisterInstance<IStartUpInfoProvider>(startUpInfoProviderMock.
Object);
Locators and naming conventions
Catel contains several "locators". These locators are services that allow a developer to resolve types, such as view models, based on another
type.
ViewModelLocator
ViewLocator
UrlLocator
Naming conventions
ViewModelLocator
Starting with Catel 3.0, there are several ways to hook up a view model to the view. When a view is constructed, an MVVM behavior is added to
the view. Thanks to these MVVM behaviors, it is possible to use exactly the same logic on 3rd party controls.
Resolving by GetViewModelType() method
The first thing the view does is call the virtual method. This method returns null by default, but it is possible to override this. GetViewModelType
For example, to let a view know what view model it should use, the following code can be used:
public class MyView : Catel.Windows.Controls.UserControl
{
protected override Type GetViewModelType()
{
return typeof(MyCustomViewModel);
}
}
Using this way, the view will use the as a view model. MyCustomViewModel
Resolving by naming convention
If the method returns (which is the default behavior), the view will resolve the from the GetViewModelType null IViewModelLocator ServiceLocator
Note that the while using the conventions, magic words such as "View", "Control", "UserControl", "Window" and "Page" will be stripped
from the view name while locating the view model type
. Then it will use the method to resolve the view model based on the type of the view. ResolveViewModel
For example, the following view:
Catel.Examples.Views.MyView
will be resolved as:
Catel.Examples.ViewModels.MyViewModel
Manually resolving a view model using naming convention
To manually resolve a view model using naming convention, use the following code:
var viewModelLocator = ServiceLocator.Instance.ResolveType<IViewModelLocator>();
var viewModelType = viewModelLocator.ResolveViewModel(typeof(MyView));
Customizing naming conventions
By default, the uses the following naming conventions to resolve view models: IViewModelLocator
[UP].ViewModels.[VW]ViewModel
[UP].ViewModels.[VW]ControlViewModel
[UP].ViewModels.[VW]WindowViewModel
[UP].ViewModels.[VW]PageViewModel
[AS].ViewModels.[VW]ViewModel
[AS].ViewModels.[VW]ControlViewModel
[AS].ViewModels.[VW]WindowViewModel
[AS].ViewModels.[VW]PageViewModel
However, it is possible to add or remove new naming conventions to support your own naming convention. For example, to add a new naming
convention for a different assembly, use this code:
var viewModelLocator = ServiceLocator.Instance.ResolveType<IViewModelLocator>();
viewModelLocator.NamingConventions.Add("MyCustomAssembly.ViewModels.[VW]ViewModel");
Registering custom view models
Sometimes, a class doesn't follow a naming convention (for whatever reason possible). In such a case, it is possible to register a mapping
manually using the following code:
var viewModelLocator = ServiceLocator.Instance.ResolveType<IViewModelLocator>();
viewModelLocator.Register(typeof(MyViewNotFollowingNamingConvention),
typeof(MyViewModel));
Using a custom ViewModelLocator
If you want to have total freedom to determine which view model is provided per view (maybe there are other services that have an impact on
this), it is possible to create a custom implementation. Then the only thing to do is to register it using the following code: IViewModelLocator
For more information about naming conventions, see Naming conventions
ServiceLocator.Default.Register<IViewModelLocator, MyViewModelLocator>();
Using a generic implementation of the view
Last but not least, it is still possible to use the "old-fashioned" way by using the generic view bases. These classes directly derive from the
non-generic views and return the generic type definition of the view model using the method. GetViewModelType
ViewLocator
The class is responsible for resolving the right views for a view model. Before Catel 3.0, the was responsible for IViewLocator IUIVisualizerService
resolving the view, but this responsibility is now taken over by the . The internally uses the to IViewLocator UIVisualizerService IViewLocator
resolve the views.
Resolving by naming convention
It is possible to resolve views using the . Then you can use the method to resolve the view based on the type of the IViewLocator ResolveView
view model.
For example, the following view model:
Catel.Examples.ViewModels.MyViewModel
will be resolved as:
Catel.Examples.Views.MyView
Manually resolving a view using naming convention
To manually resolve a view using naming convention, use the following code:
var viewLocator = ServiceLocator.Instance.ResolveType<IViewLocator>();
var viewType = viewLocator.ResolveView(typeof(MyViewModel));
Customizing naming conventions
By default, the IViewLocator uses the following naming conventions to resolve views:
[UP].Views.[VM]
[UP].Views.[VM]View
[UP].Views.[VM]Control
[UP].Views.[VM]Window
[UP].Views.[VM]Page
[UP].Controls.[VM]
[UP].Controls.[VM]Control
[UP].Pages.[VM]
[UP].Pages.[VM]Page
[UP].Windows.[VM]
[UP].Windows.[VM]Window
[AS].Views.[VM]
[AS].Views.[VM]View
[AS].Views.[VM]Control
[AS].Views.[VM]Page
[AS].Views.[VM]Window
[AS].Controls.[VM]
[AS].Controls.[VM]Control
[AS].Pages.[VM]
[AS].Pages.[VM]Page
[AS].Windows.[VM]
[AS].Windows.[VM]Window
[AS].UI.Views.[VM]
[AS].UI.Views.[VM]View
[AS].UI.Views.[VM]Control
[AS].UI.Views.[VM]Page
[AS].UI.Views.[VM]Window
[AS].UI.Controls.[VM]
[AS].UI.Controls.[VM]Control
[AS].UI.Pages.[VM]
[AS].UI.Pages.[VM]Page
[AS].UI.Windows.[VM]
[AS].UI.Windows.[VM]Window
However, it is possible to add or remove new naming conventions to support your own naming convention. For example, to add a new naming
convention for a different assembly, use this code:
var viewLocator = ServiceLocator.Instance.ResolveType<IViewLocator>();
viewLocator.NamingConventions.Add("MyCustomAssembly.Views.[VM]View");
Registering custom views
Sometimes, a class doesn't follow a naming convention (for whatever reason possible). In such a case, it is possible to register a mapping
manually using the following code:
var viewLocator = ServiceLocator.Instance.ResolveType<IViewLocator>();
viewLocator.Register(typeof(MyViewModelNotFollowingNamingConvention), typeof(MyView));
Using a custom ViewLocator
If you want to have total freedom to determine which view is provided per view model (maybe there are other services that have an impact on
this), it is possible to create a custom implementation. Then the only thing to do is to register it using the following code: IViewLocator
ServiceLocator.Default.Register<IViewLocator, MyViewLocator>();
UrlLocator
The class is responsible for resolving the right urls for the xaml views for a view model in navigation based applications. Before Catel IUrlLocator
3.0, the was responsible for resolving the url, but this responsibility is now taken over by the . The INavigationService IUrlLocator NavigationServic
internally uses the to resolve the views. e IUrlLocator
Resolving by naming convention
It is possible to resolve views using the . Then you can use the method to resolve the url based on the type of the view IUrlLocator ResolveUrl
model.
For example, the following view model:
Catel.Examples.ViewModels.MyViewModel
will be resolved as:
For more information about naming conventions, see Naming conventions
/Views/MyPage.xaml
Manually resolving a naming convention
To manually resolve a naming convention, use the following code:
var urlLocator = ServiceLocator.Instance.ResolveType<IUrlLocator>();
var url = viewLocator.ResolveUrl(typeof(MyViewModel));
Customizing naming conventions
By default, the uses the following naming conventions to resolve urls: IUrlLocator
/Views/[VM].xaml
/Views/[VM]View.xaml
/Views/[VM]Control.xaml
/Views/[VM]Page.xaml
/Views/[VM]Window.xaml
/Controls/[VM].xaml
/Controls/[VM]Control.xaml
/Pages/[VM].xaml
/Pages/[VM]Page.xaml
/Windows/[VM].xaml
/Windows/[VM]Window.xaml
/UI.Views/[VM].xaml
/UI.Views/[VM]View.xaml
/UI.Views/[VM]Control.xaml
/UI.Views/[VM]Page.xaml
/UI.Views/[VM]Window.xaml
/UI.Controls/[VM].xaml
/UI.Controls/[VM]Control.xaml
/UI.Pages/[VM].xaml
/UI.Pages/[VM]Page.xaml
/UI.Windows/[VM].xaml
/UI.Windows/[VM]Window.xaml
However, it is possible to add or remove new naming conventions to support your own naming convention. For example, to add a new naming
convention for a different assembly, use this code:
var urlLocator = ServiceLocator.Instance.ResolveType<IUrlLocator>();
urlLocator.NamingConventions.Add("/MyPages/[VM]Page.xaml");
Registering custom urls
Sometimes, a class doesn't follow a naming convention (for whatever reason possible). In such a case, it is possible to register a mapping
manually using the following code:
var urlLocator = ServiceLocator.Instance.ResolveType<IUrlLocator>();
urlLocator.Register(typeof(MyViewModelNotFollowingNamingConvention),
"/MyVerySpecialUrl.xaml");
Note that the checks whether the resource actually exists. If the resource does not exists, it will not be able to resolve a view UrlLocator
For more information about naming conventions, see Naming conventions
Using a custom UrlLocator
If you want to have total freedom to determine which url is provided per view model (maybe there are other services that have an impact on this),
it is possible to create a custom implementation. Then the only thing to do is to register it using the following code: IUrlLocator
ServiceLocator.Default.Register<IUrlLocator, MyUrlLocator>();
Naming conventions
Some services in Catel support naming conventions. For example, the and allow naming conventions to prevent IViewLocator IViewModelLocator
a user from having to register all views and view models. Internally, the naming conventions are resolved using the helper NamingConvention
class. This part of the documentation explains the possible constants in naming conventions.
[AS] constant
The [AS] constant will be replaced by the assembly name. For example, the following naming convention:
[AS].Views
in assembly will be resolved as: Catel.Examples
Catel.Examples.Views
[VM] constant
The [VM] constant will be replaced by the name of the view model without the postfix. For example, the following naming convention: ViewModel
[AS].Views.[VM]View
in assembly and for type will be resolved as: Catel.Examples Catel.Examples.ViewModels.MyViewModel
Catel.Examples.Views.MyView
[VW] constant
The [VW] constant will be replaced by the name of the view without the , , or postfixes. For example, the following View Control Page Window
naming convention:
[AS].ViewModels.[VW]ViewModel
in assembly and for type will be resolved as: Catel.Examples Catel.Examples.Views.MyView
Catel.Examples.ViewModels.MyViewModel
[UP] constant
Sometimes it is not possible to use the [AS] constant because the assembly name is not used in the namespace. For example, for an application
called where the client assembly is , the root namespace will still be . Therefore, it is PersonApplication PersonApplication.Client PersonApplication
recommend to use the [UP] constant for this situation.
The [UP] constant will move the namespaces up by one step. It automatically detects the right separator (\ (backslash), / (slash), . (dot) and |
(pipe) are supported).
The following naming convention:
[UP].Views.[VM]View
for type will be resolved as: Catel.Examples.ViewModels.MyViewModel
Catel.Examples.Views.MyView
Services
Catel offers lots of out-of-the-box services and implementations. Services can be used to separate the logic of external functionality in a service.
The advantages of this technique are:
Separation of Concerns
Unit testing (mocking interfaces is easy)
All services provided by Catel have both a unit test version and a real (or production version). For example, the has a test IMessageService
implementation that does not show a message box, but checks the result only.
AccelerometerService
CameraService
CompassService
GyroscopeService
LocationService
MessageService
NavigationService
OpenFileService
PleaseWaitService
ProcessService
SaveFileService
SchedulerService
SelectDirectoryService
SplashScreenService
UIVisualizerService
VibrateService
ViewExportService
AccelerometerService
The allows a developer to access the accelerometer of a Windows Phone device. IAccelerometerService
Platform info
Check if the sensor is supported by the device
Starting the service
Stopping the service
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
It is important that the service must be started and stopped to retrieve values
Windows RT
Test/emulation service
Check if the sensor is supported by the device
It is important to check whether the sensor is actually supported by the device. This can be done using the following code:
var accelerometerService = GetService<IAccelerometerService>();
if (accelerometerService.IsSupported)
{
// Sensor is supported
}
Starting the service
When a service is started, the service will start raising the event as soon as new values come in from the sensor. To start CurrentValueChanged
the service, use the following code:
var accelerometerService = GetService<IAccelerometerService>();
accelerometerService.CurrentValueChanged += OnAccelerometerValueChanged;
accelerometerService.Start();
Stopping the service
It is important to stop the service when it is no longer needed by the application. To stop the service, use the following code:
var accelerometerService = GetService<IAccelerometerService>();
accelerometerService.CurrentValueChanged -= OnAccelerometerValueChanged;
accelerometerService.Stop();
CameraService
The allows a developer to use the class in an MVVM manner. ICameraService PhotoCamera
Platform info
Starting and stopping the service
Capturing images
Showing a video of camera in a view
Instantiating the test implementation
Customizing camera settings for testing
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
It is important that the service must be started and stopped to retrieve values
There is also an article available about this service: WP7 Mango and Unit Testing the Camera
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Starting and stopping the service
The documentation continuously states that the camera object must be created and disposed properly. In the service, this is PhotoCamera
encapsulated by the and methods. To start the service, use the code below: Start Stop
var cameraService = GetService<ICameraService>();
cameraService.CaptureThumbnailAvailable += OnCameraServiceCaptureThumbnailAvailable;
cameraService.CaptureImageAvailable += OnCameraServiceCaptureImageAvailable;
cameraService.Start();
To stop the service, use the code below: (note: the method on a view model is feature of Catel): Close
protected override void Close()
{
var cameraService = GetService<ICameraService>();
cameraService.Stop();
cameraService.CaptureThumbnailAvailable -=
OnCameraServiceCaptureThumbnailAvailable;
cameraService.CaptureImageAvailable -= OnCameraServiceCaptureImageAvailable;
}
Capturing images
To capture images, several things must be done. The first action to accomplish is to subscribe to the eve ICameraService.CaptureImageAvailable
nt. The next step is to invoke the method like shown below: CaptureImage
CameraService.CaptureImage();
The last part is very important. You will need to read the image stream from the event: CaptureImageAvailable
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(e.ImageStream);
Showing a video of camera in a view
To show a preview of the camera input on the phone, first subscribe to the event. Next step is ICameraService.CaptureThumbnailImageAvailable
to create a property on the view model:
/// <summary>
/// Gets or sets the current photo.
/// </summary>
public BitmapImage CurrentPhoto
{
get { return GetValue<BitmapImage>(CurrentPhotoProperty); }
set { SetValue(CurrentPhotoProperty, value); }
}
/// <summary>
/// Register the CurrentPhoto property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentPhotoProperty =
RegisterProperty("CurrentPhoto", typeof(BitmapImage));
This property definition is a Catel property, but if you prefer using a different MVVM framework or your own property definition style, you are free
to do that as well.
In the view, use the Image control to show the current photo:
<Image Grid.Row="0" Source="{Binding CurrentPhoto}" />
Last but not least, we need to update the property when a new thumbnail is available. CurrentPhoto
private void OnCameraServiceCaptureThumbnailAvailable(object sender,
ContentReadyEventArgs e)
{
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(e.ImageStream);
CurrentPhoto = bitmap;
}
Instantiating the test implementation
The test implementation of the needs to be instantiated with an image. The service will use this image to create an animation. ICameraService
The animation that will be applied is the image scrolling to the right pixel by pixel.
To instantiate the test service, add an image to the Windows Phone project and set its build action to Resource. Then instantiate the service like
in the code below:
var testImage = new BitmapImage();
var streamResourceInfo = Application.GetResourceStream(new
Uri("/MyAssembly;component/Resources/Images/MyImage.png",
UriKind.RelativeOrAbsolute));
testImage.CreateOptions = BitmapCreateOptions.None;
testImage.SetSource(streamResourceInfo.Stream);
_testCameraService = new CameraService(testImage);
serviceLocator.RegisterInstance<ICameraService>(_testCameraService);
By default, the will generate a new thumbnail image every 50 milliseconds. It is possible to customize this with a constructor ICameraService
overload.
Customizing camera settings for testing
Sometimes it is required to test different resolutions. One way to do this is to buy all available Windows Phone devices and test the software on all
the cameras. An easier way is to use the and customize the camera options to test how the application responds to the different ICameraService
settings.
The settings are stored in the class. This class allows customizing all the properties normally found on the CameraServiceTestData PhotoCamera
class. For example, to only allow the primary camera (because front facing cameras are not supported by all devices), use the following code:
var cameraTestSettings = new CameraServiceTestData();
cameraTestSettings.SupportedCameraTypes = CameraType.Primary;
cameraService.UpdateTestData(cameraTestSettings);
It is also possible to change the thumbnail and final resolution of the images:
var cameraTestSettings = new CameraServiceTestData();
cameraTestSettings.PreviewResolution = new Size(400, 800);
cameraTestSettings.Resolution = new Size(1200, 2400);
cameraService.UpdateTestData(cameraTestSettings);
CompassService
The allows a developer to access the compass of a Windows Phone device. ICompassService
Platform info
Check if the sensor is supported by the device
Starting the service
Stopping the service
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Check if the sensor is supported by the device
It is important to check whether the sensor is actually supported by the device. This can be done using the following code:
Note that the Compass service is available as separate assembly to make sure does also work on older devices without a Catel.MVVM
Compass
It is important that the service must be started and stopped to retrieve values
var compassService = GetService<ICompassService>();
if (compassService.IsSupported)
{
// Sensor is supported
}
Starting the service
When a service is started, the service will start raising the event as soon as new values come in from the sensor. To start CurrentValueChanged
the service, use the following code:
var compassService = GetService<ICompassService>();
compassService.CurrentValueChanged += OnCompassValueChanged;
compassService.Start();
Stopping the service
It is important to stop the service when it is no longer needed by the application. To stop the service, use the following code:
var compassService = GetService<ICompassService>();
compassService.CurrentValueChanged -= OnCompassValueChanged;
compassService.Stop();
GyroscopeService
The allows a developer to access the gyroscope of a Windows Phone device. IGyroscopeService
Platform info
Check if the sensor is supported by the device
Starting the service
Stopping the service
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Note that the Gyroscope service is available as separate assembly to make sure does also work on older devices without Catel.MVVM
a Gyroscope
It is important that the service must be started and stopped to retrieve values
Check if the sensor is supported by the device
It is important to check whether the sensor is actually supported by the device. This can be done using the following code:
var gyroscopeService = GetService<IGyroscopeService>();
if (gyroscopeService.IsSupported)
{
// Sensor is supported
}
Starting the service
When a service is started, the service will start raising the event as soon as new values come in from the sensor. To start CurrentValueChanged
the service, use the following code:
var gyroscopeService = GetService<IGyroscopeService>();
gyroscopeService.CurrentValueChanged += OnGyroscopeValueChanged;
gyroscopeService.Start();
Stopping the service
It is important to stop the service when it is no longer needed by the application. To stop the service, use the following code:
var gyroscopeService = GetService<IGyroscopeService>();
gyroscopeService.CurrentValueChanged -= OnGyroscopeValueChanged;
gyroscopeService.Stop();
LocationService
The allows a developer to use GPS devices inside a view model. ILocationService
Platform info
Starting the service
Stopping the service
Emulating GPS without device
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Starting the service
It is important that the service must be started and stopped to retrieve values
The GPS service needs to be started and stopped. To start the GPS service, use the following code:
var locationService = GetService<ILocationService>();
locationService.LocationChanged += OnCurrentLocationChanged;
locationService.Start();
The service will raise the event when a new location becomes available. LocationChanged
Stopping the service
It is required to stop the service when it is no longer needed. The service can be stopped using the following code:
var locationService = GetService<ILocationService>();
locationService.LocationChanged -= OnCurrentLocationChanged;
locationService.Stop();
Emulating GPS without device
It is possible to emulate GPS without actually owning a Windows Phone 7 or emulate data in the emulator. To accomplish this, it is required to use
the class. This class can be used in the following way: Catel.MVVM.Services.Test.LocationService
Test.LocationService service = (Test.LocationService)GetService<ILocationService>();
// Queue the next location (and then wait 5 seconds)
var locationTestData = new LocationTestData(new Location(100d, 100d), new TimeSpan(0,
0, 0, 5)));
service.ExpectedLocations.Add(locationTestData);
// Go to the next location manually
service.ProceedToNextLocation();
It is also possible to enqueue lots of coordinates with a time span and emulate a path.
MessageService
The allows a developer to show message boxes from a view model. IMessageService
Platform info
Screenshot
Showing a message
Showing an error
Requesting confirmation
Asynchronous confirmation
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Screenshot
Showing a message
To show a message from a view model, use the following code:
var messageService = GetService<IMessageService>();
messageService.Show("My first message via the service");
Showing an error
Showing a warning or error is very easy. Use the following code:
var messageService = GetService<IMessageService>();
messageService.ShowError("Whoops, something went wrong");
Requesting confirmation
It is also possible to request confirmation from the user. The number of possibilities depends on WPF, Silverlight or Windows Phone is used (for
example, not all platforms support ). YesNo
The following code must be used to request confirmation:
var messageService = GetService<IMessageService>();
if (messageService.Show("Are you sure you want to do this?", "Are you sure?",
MessageButton.YesNo) == MessageResult.Yes)
{
// Do it!
}
Asynchronous confirmation
Sometimes you don't want to use regular message boxes, and in Silverlight this means that your call has to be asynchronous. The
implementation is very simple:
var messageService = GetService<IMessageService>();
messageService.Show("Are you sure you want to do this?", "Are you sure?",
MessageButton.YesNo, OnMessageServiceComplete);
There are two possible callbacks, one with a result of type or one without a result of type Action. Func<MessageResult>
NavigationService
The allows a developer to navigate to other pages inside an application using view models only. INavigationService
All pages will have to be registered manually or following the right naming convention.
Platform info
Closing an application
Preventing an application to be closed
Navigating to a new view
Navigating with parameters
Navigating back and forward
Navigating to a custom Uri
Registering custom views
Using naming conventions to find pages
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Closing an application
It is possible to close an application using the following code:
var navigationService = GetService<INavigationService>();
navigationService.CloseApplication();
Preventing an application to be closed
To prevent an application to be closed, one can subscribe to the event: ApplicationClosing
var navigationService = GetService<INavigationService>();
navigationService.ApplicationClosing += (sender, e)
=>
{
e.Cancel = true;
};
Navigating to a new view
To navigate to a new page, use the following code:
For WPF and Silverlight, the pages must inherit from . For WP7, the pages must inherit from . In WPF, the Page PhoneApplicationPage
parameters are of type , in Silverlight and WP7, the arguments are of type . Dictionary<string, object> Dictionary<string, string>
var navigationService = GetService<INavigationService>();
navigationService.Navigate<EmployeeViewModel>();
Navigating with parameters
It is very easy to navigate to a new page with parameters. Use the following code:
var parameters = new Dictionary<string, object>();
parameters.Add("id", employee.EmployeeID);
var navigationService = GetService<INavigationService>();
navigationService.Navigate<EmployeeViewModel>(parameters);
To read the navigation parameters in the receiving view model, use the method. OnNavigationCompleted
Navigating back and forward
The service also supports navigating back and forward:
var navigationService = GetService<INavigationService>();
navigationService.GoBack(); // navigates to the previous page, obviously
navigationService.GoForward(); // navigates to the next page, obviously
Navigating to a custom Uri
To navigate to a custom uri without a view model type, use the following code. Of course it's also possible to pass parameters using the right
method overload.
var navigationService = GetService<INavigationService>();
navigationService.Navigate("/UI/Pages/EmployeePage.xaml");
Registering custom views
To register custom views, use the following code:
var navigationService = GetService<INavigationService>();
navigationService.Register(typeof(EmployeeViewModel), typeof(EmployeeDetailsPage));
Using naming conventions to find pages
If you use a consistent naming convention for views, it is possible to apply this naming convention to the service. This saves a lot of custom
registration. When a page is not registered, the method will try to find the view using the naming convention. Show
To add a naming convention, use the following code:
var navigationService = GetService<INavigationService>();
navigationService.NamingConventions.Add(string.Format("/Views/My{0}View",
NamingConvention.ViewModelName));
The above naming convention will use the following combination:
Input: MyAssembly.UI.ViewModels.EmployeeViewModel
Output: MyAssembly.UI.Windows.EmployeeWindow
By default, the following naming conventions will be used:
/Views/[VM].xaml
/Views/[VM]View.xaml
/Views/[VM]Control.xaml
/Views/[VM]Page.xaml
/Views/[VM]Window.xaml
/Controls/[VM].xaml
/Controls/[VM]Control.xaml
/Pages/[VM].xaml
/Pages/[VM]Page.xaml
/Windows/[VM].xaml
/Windows/[VM]Window.xaml
/UI/Views/[VM].xaml
/UI/Views/[VM]View.xaml
/UI/Views/[VM]Control.xaml
/UI/Views/[VM]Page.xaml
/UI/Views/[VM]Window.xaml
/UI/Controls/[VM].xaml
/UI/Controls/[VM]Control.xaml
/UI/Pages/[VM].xaml
/UI/Pages/[VM]Page.xaml
/UI/Windows/[VM].xaml
/UI/Windows/[VM]Window.xaml
OpenFileService
The allows a developer to let the user choose a file from inside a view model. IOpenFileService
Platform info
Opening a file
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Opening a file
To open a file, it is required to set the right properties of the service and then make a call to the method: DetermineFile
var openFileService = GetService<IOpenFileService>();
openFileService.Filter = "All files|*.*";
if (openFileService.DetermineFile())
{
// User selected a file
}
PleaseWaitService
The allows a developer to show a please wait message (a.k.a. busy indicator) from a view model. IPleaseWaitService
Platform info
Screenshot
Showing
Hiding
Showing and automatically hide
Changing the status
Showing a determinate please wait window
Push/Pop
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Screenshot
Showing
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Show();
Hiding
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Hide();
Showing and automatically hide
This is the WPF service screenshot, the exact look differs per framework
The can automatically hide itself when an action is completed. To use this feature, simply pass a delegate to the metho IPleaseWaitService Show
d and the service will hide the window as soon as the delegate has completed.
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Show(() => Thread.Sleep(1500));
Changing the status
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.UpdateStatus("new status");
Showing a determinate please wait window
By default, the shows an indeterminate state (no actual progress is visible). However, both the Silverlight and WPF IPleaseWaitService
implementation of the service also implement the status that shows the progress of a long running action.
The method can be used to show the window. The argument can contain '{0}' (represents the current item) and '{1}' UpdateStatus statusFormat
(represents the total items). However, they can also be left out.
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.UpdateStatus(1, 5, "Updating item {0} of {1}");
The determinate version can be hidden via a call to or when the currentItem argument is larger than the number of totalItems. Hide
Push/Pop
Sometimes, multiple view models or multiple actions use the service. It's not possible to hide the window when the first action is completed,
because the user will still have to wait for the other actions to complete (without a please wait window). To implement this correctly, it is possible
to use the and methods. Push Pop
The method shows the window if it is not already visible and then increases an internal counter. At the start of each (asynchronous) action, Push
the developer can call the method. When the action is completed, the developer calls which will internally decrease the counter. If the Push Pop
counter hits zero (0), the window is automatically hidden.
It is possible to hide the window, even when the internal counter is not yet zero. A call to will reset the counter to zero and thus hide the Hide
window.
ProcessService
The allows a developer to run processes from inside a view model. IProcessService
Platform info
Starting a process with arguments
Starting a process with arguments and completed callback
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Starting a process with arguments
To start a process with arguments, use the following code:
var processService = GetService<IProcessService>();
processService.Start("notepad.exe", @"C:\mytextfile.txt");
Starting a process with arguments and completed callback
To start a process with arguments and receive a callback on completion, use the following code:
var processService = GetService<IProcessService>();
processService.Start("notepad.exe", @"C:\mytextfile.txt", OnProcessCompleted);
SaveFileService
The allows a developer to let the user choose a file from inside a view model. ISaveFileService
Platform info
Choosing a file
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Choosing a file
To select a file to save, it is required to set the right properties of the service and then make a call to the method: DetermineFile
var saveFileService = GetService<ISaveFileService>();
saveFileService.Filter = "C# File|*.cs";
if (saveFileService.DetermineFile())
{
// User selected a file
}
SchedulerService
The allows a developer to schedule an action in the relative or absolute future. The will use the ISchedulerService SchedulerService DispatcherTi
to invoke the action. mer
Platform info
Scheduling an action in the relative future
Scheduling an action in the absolute future
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Scheduling an action in the relative future
To schedule an action in the relative future, use the method with the overload. The code below starts the action with a delay Schedule TimeSpan
of 50 milliseconds.
var schedulerService = GetService<ISchedulerService>();
schedulerService.Schedule(() => DoSomething(), new TimeSpan(0, 0, 0, 0, 50));
Scheduling an action in the absolute future
To schedule an action in the absolute future, use the method with the overload. The code below starts the action in 5 Schedule DateTime
minutes.
var schedulerService = GetService<ISchedulerService>();
schedulerService.Schedule(() => DoSomething(), DateTime.Now.AddMinutes(5));
SelectDirectoryService
The allows a developer to let the user choose a directory from inside a view model. ISelectDirectoryService
Platform info
Selecting a directory
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Note that the does not provide any persistence of actions and schedules. When the application is closed, all SchedulerService
schedules are lost because they are kept in memory.
Windows RT
Test/emulation service
Selecting a directory
To select a directory, it is required to set the right properties of the service and then make a call to the method: DetermineDirectory
var selectDirectoryService = GetService<ISelectDirectoryService>();
if (selectDirectoryService.DetermineFile())
{
// User selected a directory
}
SplashScreenService
The allows a developer execute a batch of tasks en-queued and show the progress through a registered ISplashScreenService IPleaseWaitServi
or type, from the main entry point of the application (typically from the bootstrapper) or a view model. ce IUIVisualizerService
Platform info
Screenshot
Enqueuing tasks into the SplashScreenService batch
Committing the batch
Committing the batch asynchronously with callback
Smooth progress notification of an executing action task
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Screenshot
Enqueuing tasks into the SplashScreenService batch
First of all is requiered resolve the instance of the registered type from the view model: ISplashScreenService
var splashScreenService = GetService<ISplashScreenService>();
or from the application bootstrapper, after a previous service registration:
ServiceLocator.Instance.RegisterTypeIfNotYetRegistered<ISplashScreenService,
SplashScreenService>();
/*...*/
var splashScreenService = ServiceLocator.Instance.Resolve<ISplashScreenService>();
To en-queue tasks use the following code:
splashScreenService.Enqueue(new ActionTask("Creating the shell", OnCreateShell));
splashScreenService.Enqueue(new ActionTask("Initializing modules",
OnInitializeModules));
splashScreenService.Enqueue(new ActionTask("Starting application",
OnStartApplication));
Committing the batch
To execute the batch of en-queued tasks, and show the progress through the , use the following code: IPleaseWaitService
splashScreenService.Commit();
To execute the batch of en-queued tasks, and show the progress through the , use the following code: IUIVisualizerService
splashScreenService.Commit<MySplashScreenViewModel>();
or:
splashScreenService.Commit(typeof(MySplashScreenViewModel));
Committing the batch asynchronously with callback
To execute the batch of en-queued tasks asynchronously, and show the progress through the , use the following code: IPleaseWaitService
splashScreenService.CommitAsync(OnBatchCompleted);
To execute the batch of en-queued task asynchronously, and show the progress through the , use the following code: IUIVisualizerService
splashScreenService.CommitAsync<MySplashScreenViewModel>(OnBatchCompleted);
or:
splashScreenService.CommitAsync(OnBatchCompleted, typeof(MySplashScreenViewModel));
Smooth progress notification of an executing action task
To notify the progress of an action task smoothly, use the argument of type just like is used in the following code: ITaskProgressTracker
private void OnInitializeModules(ITaskProgressTracker tracker)
{
int registeredModulesCount = 0;
foreach (var module in ModuleCatalog.Modules)
{
tracker.UpdateStatus((int)(100.0f * (registeredModulesCount /
ModuleCatalog.Modules.Count)), string.Format("Registering module '{0}'",
module.ModuleName));
if (RegisterModule(module))
{
registeredModulesCount++;
}
}
}
UIVisualizerService
The allows a developer to show (modal) windows or dialogs without actually referencing a specific view. Internally, the IUIVisualizerService UIVisu
uses the to resolve views. alizerService ViewLocator
Platform info
Screenshot
Showing a non-modal window
Showing a modal window
Showing a window with callback
Registering a window
Using naming conventions to find windows
After committing the batch is cleared, so to execute it again you should en-queue the tasks again
The SplashScreenService is thread-safe
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Screenshot
Showing a non-modal window
To show a non-modal window, use the following code:
var viewModel = new EmployeeViewModel();
var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.Show(viewModel);
Showing a modal window
To show a modal window, use the following code:
var viewModel = new EmployeeViewModel();
var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.ShowDialog(viewModel);
Showing a window with callback
To show a (modal or non-modal) window and get a callback as soon as the window is closed, use the following code:
var viewModel = new EmployeeViewModel();
var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.Show(viewModel, OnWindowClosed);
Registering a window
To register a custom window which is not automatically detected via reflection, it is required to use the Register method:
var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.Register(typeof(EmployeeViewModel), typeof(EmployeeView));
Using naming conventions to find windows
Please see the topic. ViewLocator
VibrateService
The allows a developer to start and stop vibration of the device via a service. IVibrateService
Platform info
Starting vibration
Stopping the vibration earlier than initially planned
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Starting vibration
To start the vibration, use the following code (will vibrate for 250 ms). Note that the time span must be between 0 and 5 seconds.
var vibrateService = GetService<IVibrateService>();
vibrateService.Start(new TimeSpan(0, 0, 0, 0, 250);
Stopping the vibration earlier than initially planned
By default, the vibration stops automatically after the specified time span has passed. However, it is possible to stop the vibration manually.
var vibrateService = GetService<IVibrateService>();
vibrateService.Stop();
ViewExportService
The IViewExportServiceallows a developer to export a specific view that belongs to a view model to the clipboard, a file or a printer.
Platform info
Exporting a view
Supported export methods
Platform info
Framework Supported
WPF
Silverlight 4
Silverlight 5
Windows Phone 7
Windows Phone 8
Windows RT
Test/emulation service
Exporting a view
To export a view, use the following code:
var viewExportService = GetService<IViewExportService>();
viewExportService.Export(myViewModel, ExportMode.File);
Supported export methods
Export method WPF Silverlight
Clipboard
File
Print
View models
The view model is a very important part in the MVVM pattern. The view model is responsible for the actual logic that ensures separation of
concerns, but also allows unit testing on the view logic (which is implemented in the view model) without actually instantiating the views.
Like almost every other MVVM framework, the base class for all View-Models is . This base class is derived from the ViewModelBase ModelBase
class explained earlier in this article, which gives the following advantages:
Dependency property a-like property registration;
Automatic change notification;
Support for field and business errors.
Because the class derives from , you can simply add field and business errors that are automatically being reflected to the UI. Writing ModelBase
View-Models has never been so easy!
Creating a basic view model
Creating a view model that watches over other view models
Creating a view model with a model
Creating a view model with a model and mappings
Mapping properties from view to view model
Nested view models
Validation in view models
Advanced view models
/// <summary>
/// Gets or sets whether the user has agreed to continue.
/// </summary>
public bool UserAgreedToContinue
{
get { return GetValue<bool>(UserAgreedToContinueProperty); }
set { SetValue(UserAgreedToContinueProperty, value); }
}
/// <summary>
/// Register the UserAgreedToContinue property so it is known in the class.
/// </summary>
public static readonly PropertyData UserAgreedToContinueProperty =
RegisterProperty("UserAgreedToContinue", typeof(bool));
/// <summary>
/// Validates the fields.
/// </summary>
protected override void ValidateFields(List<FieldValidationResult>
validationResults)
{
// Check if the user agrees to continue
if (!UserAgreedToContinue)
{
validationResults.Add(FieldValidationResult.CreateError(UserAgreedToContinueProperty,
"User must agree to continue");
}
}
}
if (string.IsNullOrWhiteSpace(LastName))
{
validationResults.Add(FieldValidationResult.CreateError(LastNameProperty,
"Last name is required"));
}
}
/// <summary>
/// Saves the data.
/// </summary>
/// <returns>
/// <c>true</c> if successful; otherwise <c>false</c>.
/// </returns>
protected override bool Save()
{
// Save the data manually to the model
Person.FirstName = FirstName;
Person.LastName = LastName;
[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewModelToView)]
public GeoCoordinate MapCenter
{
get { return (GeoCoordinate) GetValue(MapCenterProperty); }
set { SetValue(MapCenterProperty, value); }
}
MVVM behaviors
Starting with Catel 2.0, it is possible to use the logic of the following controls as a behavior:
DataWindow => WindowBehavior
UserControl => UserControlBehavior
Page => NavigationPageBehavior
This means that you no longer have to derive user controls from the to use the ability to solve the nested user controls problem. Or, if UserControl
you are not happy with the endless possibilities of the , why not just creating a custom one without having to think about the MVVM DataWindow
integration.
WindowBehavior
UserControlBehavior
NavigationPageBehavior
WindowBehavior
The class takes care of all the MVVM integrations of a window and a view model. So, where you previously had to derive a WindowBehavior
Window implementation from , you can now create a new Window like any application and then add this: DataWindow
The and properties are not obligate, and need the format of . By default, the Click event is used, so if a button Save Cancel [controlname].[event]
(or another control that should respond using the event), the is sufficient. Click [controlname]
<i:Interaction.Behaviors>
<catel:WindowBehavior ViewModelType="viewmodels:DemoWindowViewModel"
Save="okButton.Click" Cancel="cancelButton.Click" />
</i:Interaction.Behaviors>
Seems too easy right? Well, it is really all you have to do.
This looks great, but why is there still a with this terrific solution? DataWindow
First of all, we have to think about all the people that are already using Catel. We don't want to break their code and provide backward
compatibility. Updates to the logic will be applied to both the behavior and the view base because the logic is locatedin a separate class. Also, the
is truly a terrific class, which supports lots of customization and takes care of dumb generation of buttons and the DataWindow InfoBarMessageC
. ontrol
UserControlBehavior
The UserControlBehavior class takes care of all the MVVM integrations of a user control and a view model. So, where you previously had to
derive a UserControl implementation from UserControl, you can now create a new UserControl like any application and then add this:
<i:Interaction.Behaviors>
<catel:UserControlBehavior ViewModelType="viewmodels:DemoWindowViewModel" />
</i:Interaction.Behaviors>
This looks great, but why is there still a with this terrific solution? UserControl
For more information, check out the the which shows the differences Catel.Examples.WPF.AdvancedDemo
First of all, we have to think about all the people that are already using Catel. We don't want to break their code and provide backward
compatibility. Also, the implements the interface which allows chained view models in a hierarchy. If you don't UserControl IViewModelContainer
need this, just go for the behavior. If you need hierarchy chains, either let the custom implement it or use the . UserControl UserControl
To support nested user controls and their validation, it is important to chain views together using the interface. You can IViewModelContainer
choose not to do this, but then it is important to disable for performance reasons (otherwise, the behavior will SupportParentViewModelContainers
keep searching the visual tree for the parent view model).
NavigationPageBehavior
The class takes care of all the MVVM integrations of a page (used in navigation or browser based applications) and a NavigationPageBehavior
view model. So, where you previously had to derive a implementation from , you can now create a new like any application and Page Page Page
then add this:
<i:Interaction.Behaviors>
<catel:NavigationPageBehavior ViewModelType="viewmodels:DemoWindowViewModel" />
</i:Interaction.Behaviors>
This looks great, but why is there still a Page with this terrific solution?
First of all, we have to think about all the people that are already using Catel. We don't want to break their code and provide backward
compatibility. Also, the class comes with more default functionality that you might be interested in. Page
Validation controls
There are some very important controls in Catel which help with visualizing the validation results.
InfoBarMessageControl
WarningAndErrorValidator
InfoBarMessageControl
Ever wanted to show the details of error messages to your end-users? Then, the is the control to use! The control shows InfoBarMessageControl
a summary of all business and field errors provided by bindings on objects that implement the interface. IDataErrorInfo
In combination with the control, the can even show field and business warnings for objects that WarningAndErrorValidator InfoBarMessageControl
implement the interface that ships with Catel. IDataWarningInfo
<catel:InfoBarMessageControl>
<!-- Actual content here -->
</catel:InfoBarMessageControl>
The subscribes to the class. This class is responsible for showing the red border around the controls that WPF InfoBarMessageControl Validation
shows by default. Then, it requests the actual field error property of the data item. This is added to an internal collection of error messages, and
For more information, check out the the which shows the differences Catel.Examples.WPF.AdvancedDemo
For more information, check out the the which shows the differences Catel.Examples.WPF.BrowserApplication
therefore the control is able to show the errors of all bindings.
When the control is found as a child control, the also subscribes to the events exposed by the WarningAndErrorValidator InfoBarMessageControl
. The internal working of that control is explained later in this article. When a data object is subscribed via the WarningAndErrorValidator WarningA
, the will also handle the warnings and business errors of that data object. ndErrorValidator InfoBarMessageControl
WarningAndErrorValidator
The control is not visible to the end user. The only thing this control takes care of is to forward business errors and WarningAndErrorValidator
warnings to controls that are interested in them. The only control that ships with Catel is the . Thanks to the InfoBarMessageControl WarningAndE
, the is able to show business errors and warnings to the end user. rrorValidator InfoBarMessageControl
<catel:WarningAndErrorValidator Source="{Binding MyObject}" />
The needs to be placed inside an . The control then subscribes to all property changed events WarningAndErrorValidator InfoBarMessageControl
to make sure it receives all change notifications. Then, on every property change, the control checks whether the sender either implements the ID
or interfaces. ataErrorInfo IDataWarningInfo
When an error or warning is found on the changed property, the control invokes the corresponding events so the can InfoBarMessageControl
show the right information. When an error or warning no longer exists in a model, a event is invoked so the kno Removed InfoBarMessageControl
ws that the error or warning should be removed from the summary.
Finding the view of a view model
Sometimes it is required to find the view of a view model. For example, this comes in handy when implementing drag and drop where you only
want to support code via view models.
Internally, Catel uses with the for this. As soon as a view is loaded (via the Loaded event), the view is registered to the view IViewManager
manager. The view manager will keep an eye on the events of the view and notice view model changes.
A view is removed from the manager as soon as it is unloaded (via the event). From this moment on, it is no longer possible to retrieve Unloaded
a view via its view model.
Retrieving the view of a view model
To find the view of a view model, use the steps below:
1) Resolve the view from from the : IViewManager ServiceLocator
var viewManager = ServiceLocator.Default.ResolveType<IViewManager>();
2) Resolve the view:
var views = viewManager.GetViewsOfViewModel(myViewModel);
Using external controls
Catel ships with default controls and the . However, some people rather use controls and windows from Telerik, DevExpress or some DataWindow
other 3rd party controls. This is perfectly possible and it is pretty easy to let those external controls support MVVM in the "Catel way".
The logic can be implemented via the MVVM behaviors that ship with Catel. In fact, the and of Catel also use these DataWindow UserControl
behaviors to implement their logic.
Using a custom control
Using a custom window
Using a custom control
Remember that only controls implementing are supported by the IView IViewManager
Note that it is possible that multiple views are linked to the same view model
In this part of the documentation, the of Telerik will be used as an example on how to create a that behaves like the RadTabItem RadTabItem Use
. rControl
Creating the base class with behavior
Using the class
Creating the base class with behavior
The first thing to do is to create a new base class that accepts a view model type argument. In this example, we will call it (to make it as TabItem
"external control company independent" as possible). Below is the code for the control definition. The downside of xaml based applications is that
you cannot derive from controls or windows that have a partial class defined in xaml. Therefore, all controls and code must be initialized via code
as you can see in the code below.
/// <summary>
/// Base class for a control with the Catel mvvm behavior.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
public class TabItem : RadTabItem, IUserControl
{
private readonly UserControlLogic _logic;
/// <summary>
/// Initializes a new instance of the <see cref="TabItem{TViewModel}"/> class.
/// </summary>
public TabItem()
{
var viewModelType = GetViewModelType();
if (viewModelType == null)
{
var viewModelLocator =
ServiceLocator.Instance.ResolveType<IViewModelLocator>();
viewModelType = viewModelLocator.ResolveViewModel(GetType());
if (viewModelType == null)
{
const string error = "The view model of the view could not be resolved.
Use either the GetViewModelType() method or IViewModelLocator";
throw new NotSupportedException(error);
}
}
_logic = new UserControlLogic(this, viewModelType);
_logic.ViewModelChanged += (sender, e) => ViewModelChanged.SafeInvoke(this, e);
_logic.ViewModelPropertyChanged += (sender, e) =>
ViewModelPropertyChanged.SafeInvoke(this, e);
_logic.PropertyChanged += (sender, e) => PropertyChanged.SafeInvoke(this, e);
SetBinding(RadTabItem.HeaderProperty, new Binding("Title"));
}
/// <summary>
/// Gets the view model that is contained by the container.
/// </summary>
/// <value>The view model.</value>
public IViewModel ViewModel
{
get { return _logic.ViewModel; }
}
/// <summary>
/// Occurs when the <see cref="ViewModel"/> property has changed.
/// </summary>
public event EventHandler<EventArgs> ViewModelChanged;
/// <summary>
/// Occurs when a property on the <see cref="ViewModel"/> has changed.
/// </summary>
public event EventHandler<PropertyChangedEventArgs> ViewModelPropertyChanged;
/// <summary>
/// Occurs when a property on the container has changed.
/// </summary>
/// <remarks>
/// This event makes it possible to externally subscribe to property changes of a
<see cref="DependencyObject"/>
/// (mostly the container of a view model) because the .NET Framework does not
allows us to.
/// </remarks>
public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
}
Using the class
The class can now be used the same as the class. For more information, see . UserControl UserControl explained
Using a custom window
In this part of the documentation, the of Telerik will be used as an example on how to create a that behaves like the RadWindow WindowBase Dat
. aWindow
Creating the base class with behavior
The first thing to do is to create a new base class that accepts a view model type argument. In this example, we will call it (to make it WindowBase
as "external control company independent" as possible). Below is the code for the window definition. The downside of xaml based applications is
that you cannot derive from controls or windows that have a partial class defined in xaml. Therefore, all controls and code must be initialized via
code as you can see in the code below.
/// <summary>
/// Base class for a window with the Catel mvvm behavior.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
public class Window : RadWindow, IDataWindow
{
private readonly WindowLogic _logic;
/// <summary>
/// Initializes a new instance of the <see cref="Window{TViewModel}"/> class.
/// </summary>
public Window()
: this(null) { }
/// <summary>
/// Initializes a new instance of the <see cref="Window{TViewModel}"/> class.
/// </summary>
/// <param name="viewModel">The view model.</param>
public Window(IViewModel viewModel)
{
var viewModelType = (viewModel != null) ? viewModel.GetType() :
GetViewModelType();
if (viewModelType == null)
{
var viewModelLocator =
ServiceLocator.Instance.ResolveType<IViewModelLocator>();
viewModelType = viewModelLocator.ResolveViewModel(GetType());
if (viewModelType == null)
{
const string error = "The view model of the view could not be resolved.
Use either the GetViewModelType() method or IViewModelLocator";
throw new NotSupportedException(error);
You would expect an abstract class here, but the designers (both Visual Studio and Expression Blend) can't handle abstract base
classes
Because the of Telerik does not close the window when the is set, this window subscribes to the RadWindow DialogResult ViewModelC
event to close the window losed
}
}
_logic = new WindowBehavior(this, viewModelType, viewModel);
_logic.ViewModelChanged += (sender, e) => ViewModelChanged.SafeInvoke(this, e);
_logic.ViewModelPropertyChanged += (sender, e) =>
ViewModelPropertyChanged.SafeInvoke(this, e);
_logic.PropertyChanged += (sender, e) => PropertyChanged.SafeInvoke(this, e);
// Because the RadWindow does not close when DialogResult is set, the following
code is required
ViewModelChanged += (sender, e) => OnViewModelChanged();
// Call manually the first time (for injected view models)
OnViewModelChanged();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
SetBinding(RadWindow.HeaderProperty, new Binding("Title"));
}
/// <summary>
/// Gets the view model that is contained by the container.
/// </summary>
/// <value>The view model.</value>
public IViewModel ViewModel
{
get { return _logic.ViewModel; }
}
/// <summary>
/// Occurs when the <see cref="ViewModel"/> property has changed.
/// </summary>
public event EventHandler<EventArgs> ViewModelChanged;
/// <summary>
/// Occurs when a property on the <see cref="ViewModel"/> has changed.
/// </summary>
public event EventHandler<PropertyChangedEventArgs> ViewModelPropertyChanged;
/// <summary>
/// Occurs when a property on the container has changed.
/// </summary>
/// <remarks>
/// This event makes it possible to externally subscribe to property changes of a
<see cref="DependencyObject"/>
/// (mostly the container of a view model) because the .NET Framework does not
allows us to.
/// </remarks>
public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
private void OnViewModelChanged()
{
if (ViewModel != null && !ViewModel.IsClosed)
{
ViewModel.Closed += ViewModelClosed;
}
}
private void ViewModelClosed(object sender, ViewModelClosedEventArgs e)
{
Close();
}
}
Using the class
The class can now be used the same as the class. For more information, see . DataWindow DataWindow
Advanced information about views
If you want to know about the background information on how the and work, you've come to the right place. The UserControl DataWindow
information found in this part is pretty heavy material, so enter at your own risk...
DataWindow - under the hood
UserControl - under the hood
DataWindow - under the hood
UserControl - under the hood
The is a pretty sophisticated class. In this part of the documentation, the inner workings of the control are explained. What better way UserControl
is there than to using flowcharts. There are a few events very important for the inner workings of the user control. The flowcharts are created per
event.
Managing the custom DataContext
Main flow
Loaded
Unloaded
DataContextChanged
DetermineDataContext
Managing the custom DataContext
The logic uses an additional layer to customize the DataContext. Below is a graphical representation of how it works. UserControl
You would expect an abstract class here, but the designers (both Visual Studio and Expression Blend) can't handle abstract base
classes
This documentation has to be written in the future
Keep in mind that the actual logic is implemented in the , which is used by the . This way, the logic can be UserControlLogic UserControl
used by any user control via the . UserControlBehavior
Another view can be found in the image below:
Main flow
The following flowchart shows what happens with a user control in the main flow (the startup). First, it checks whether the user control is loaded
(which is not in a normal case). If the control is loaded, it goes directly to determining the datacontext. Otherwise, it will postpone the action until
the event. Loaded
Loaded
When the control is loaded, it starts checking for the first time whether the current datacontext can be used to create a view model. But, before it
does this, it checks whether it should (and can) re-use an existing view model. To control whether view models should be re-used, use the CloseV
property. iewModelOnUnloaded
If a view model can and should be re-used, it sets the view model as data context and that's it. If there is no view model, or the previous view
model should not be re-used, the control continues to determine the datacontext.
Unloaded
Another event that is very important is the event. In this event, the control either cleans up the view model or stores it so it can be Unloaded
re-used later. Then, it also restores the old datacontext so it never breaks existing application bindings. This way, the control won't leave any
traces behind.
DataContextChanged
The event is used to react to changes of the datacontext. You might be thinking: "there is no event in DataContextChanged DataContextChanged
Silverlight". We use the class for that. If the new datacontext is new (thus not a view model that the control just set itself), it it DataContextHelper
continues to determine the datacontext. Otherwise, it will not take any action.
DetermineDataContext
All other flowcharts eventually led to this flowchart, the determination of the datacontext. The determination of the datacontext is very important,
because this is the moment where the user control transforms the datacontext into a new view model if possible. First it tries is to construct the
view model with the datacontext. So, if the datacontext is an object of type Person, and the view model of the user control has a constructor that
accepts a Person object, it injects the datacontext into the constructor of the view model. If that fails, or there is simply no constructor, the control
checks whether the view model has an empty constructor. If so, it constructs the view model and sets it as the new datacontext. If not, it will leave
the datacontext untouched.
Basically, this is all that happens on a higher level to transform a datacontext into a view model. Under the hood, it's a bit more complicated but
again, on a higher level this is what happens.
Catel.Mvc
The MVC library provided by Catel provides code to easily combine the power of Catel with ASP.NET MVC.
Configuring dependency injection
Configuring dependency injection
Dependency injection in ASP.NET MVC controllers
Dependency injection in ASP.NET Web api controllers
Dependency injection in ASP.NET MVC controllers
The ServiceLocator is a very powerful dependency injection class. To allow the ASP.NET MVC to use the ServiceLocator as dependency
resolver, simply call the following in the global.asax class:
Catel.Mvc.DependencyInjectionConfig.RegisterServiceLocatorAsDependencyResolver();
Dependency injection in ASP.NET Web api controllers
If you are using web api as well, a few more things must be done.
1.Create new type implementing theIDependencyResolverof the web api:
public class CatelWebApiDependencyResolver : Catel.IoC.DependencyResolver,
System.Web.Http.Dependencies.IDependencyResolver
{
private readonly IServiceLocator _serviceLocator;
private readonly ITypeFactory _typeFactory;
public CatelWebApiDependencyResolver()
: this(ServiceLocator.Default) { }
public CatelWebApiDependencyResolver(IServiceLocator serviceLocator)
{
Argument.IsNotNull(() => serviceLocator);
_serviceLocator = serviceLocator;
_typeFactory = serviceLocator.ResolveType<ITypeFactory>();
}
public System.Web.Http.Dependencies.IDependencyScope BeginScope()
{
// This resolver does not support child scopes, so we simply return 'this'.
return this;
}
Catel.Extensions.Controls
Controls
Pixel shaders
StyleHelper
Themes
Controls
There are several controls available. See the child pages.
StackGrid
TabControl
TraceOutputControl
WatermarkTextBox
StackGrid
Although the example looks crappy (I am not a designer), it shows the power of the StackGrid. You don't have to specify the Grid.Row and
Grid.Column attached properties. Remember the times that you want to insert a grid and you had to increase all the numbers? From now on, use
the StackGrid!
<catel:StackGrid>
<!-- Row definitions -->
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MinHeight="15" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<!-- Column definitions -->
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</catel:StackGrid.ColumnDefinitions>
<!-- Name, will be set to row 0, column 1 and 2 -->
<Label Content="Name" />
<TextBox Text="Geert van Horrik" />
<!-- Empty row -->
<catel:EmptyRow />
<!-- Wrappanel, will span 2 columns -->
<WrapPanel Grid.ColumnSpan="2">
<Button Command="ApplicationCommands.Close" />
</WrapPanel>
</catel:StackGrid>
The Grid is an excellent control to show several controls in a nice layout on the screen. However, it happens a lot that a grid consists of only 2 or
3 columns, and the first column is for all the labels, and the second one is for controls such as textboxes. You correctly implement all the windows
and controls of your application based on user requirements, and then the user decides that he/she wants a row inserted into a grid containing
about 20 rows. When this happens, you need to re-define all the row attributes of the grid.
With the StackGrid, it is no longer required to define the row and column definitions. The StackGrid can smartly interpret the location of the
controls and therefore fill in the Grid.Row and Grid.Column attached properties for you. You need an empty row? No problem, you can use the
EmptyRow class to fill up a row for you. You want a column span? No problem, just use the existing Grid.Column attached property and the
StackGrid will automatically handle this for you.
The StackGrid internally uses a Grid to measure the layout. However, it dynamically loops through its children, and then assigns the Grid.Row
and Grid.Column attached properties for the user.
TabControl
A custom implementation of the TabControl which allows the customization of the way tab items are loaded.
The following options are available:
Single => loads the current tab
AllOnFirstUse => loads all the tabs when a tab is used for the first time
AllOnStartUp => loads all the tabs when the control is loaded
TraceOutputControl
TraceOutputControl is a debugging convenience control. It shows all the trace and logging output in a filterable control. This way, you can easily
view all the binding errors, etc., in your app instead of the non-colored output window in Visual Studio.
<Controls:TraceOutputControl />
Many times, developers are inside an application viewing the result of what they have created. But, they also want to know what is happening in
the background and view the traces they have written. The output window of Visual Studio is a solution, but it doesnt show errors very well (black,
just as the normal output). Also, it doesnt allow run-time filtering of the results.
The TraceOutputControl allows a developer to embed a control inside a window or control in the actual application and view the information when
the application is actually running. The TraceOutputControl is also available as a separate window in case it cant be embedded into the software
itself (for example, when a plug-in is being developed for a 3rd party application).
The TraceOutputControl subscribes a custom TraceListener to the Trace.Listeners collection. Then, it filters out the messages that the user
actually wants to see and stores these messages into an internal collection so the user can still filter the messages at a later time.
WatermarkTextBox
The WatermarkTextBox allows to set a watermark on textboxes that do not yet have a value.
Setting up a simple watermark
A simple watermark is a watermark with text only. Below is an example:
<catel:WatermarkTextBox Watermark="Enter the first name" />
Setting up a complex watermark
A complex watermark is a watermark that can contain any control, for example an image:
<catel:WatermarkTextBox>
<catel:WatermarkTextBox.Watermark>
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Address.png" />
<TextBlock Text="Enter the e-mail" />
</StackPanel>
</catel:WatermarkTextBox.Watermark>
</catel:WatermarkTextBox>
Pixel shaders
Catel also uses pixel shaders to apply effects to controls via themes and styles. One of the pixel shaders is, for example, the . GrayscaleEffect
This effect automatically converts an image on a button to gray scale when the button is disabled. Below is an example of the shader effect:
This documentation only applies to WPF
If there are a lot of buttons used on the screen, it might be possible that the video card does not support so many shaders, and then WPF will start
throwing exceptions. In that case, first try to set the shader mode of Catel to . If that doesnt work, you can turn the ShaderRenderMode.Software
shaders off by using . ShaderRenderMode.Off
// Force software rendering
StyleHelper.PixelShaderMode = PixelShaderMode.Software;
// Turn off
StyleHelper.PixelShaderMode = PixelShaderMode.Off;
StyleHelper
The StyleHelper class has a few static members that will create style forwarders. Style forwarders are styles that are defined on the application
level, not on the theme level. This allows you to create forwarders with the same key as the control name, but that will forward to the
DefaultxxxStyle. Since the new styles are defined at the application level, you will not get any circular references because the style defined in the
theme cannot access the application level resources.
This is accomplished by simply calling StyleHelper.CreateStyleForwardersForDefaultStyles() in the OnStartup of an application.
Catel currently ships with a several theme files. This example theme file is based on the Aero" theme that is included in the WPF libraries. The
theme of Catel corrects the margins, since the default Aero" theme sets the margin of all controls to 0, which will result in all the user controls
being stuck together, as shown in the figure below:
We see too many developers fixing the margins on the control directly, while this can also be accomplished by using the Catel theme and the
included StyleHelper class. See the image below for the final result:
Themes
Using the themes is pretty simple. First, the right theme has to be added to the application resource dictionary:
<Application x:Class="OverrideStyles.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Set custom theme -->
<ResourceDictionary
Source="/Catel.Extensions.Controls;component/themes/generic.xaml"
/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
The next step is optional. If the margins should automatically be corrected by the stylehelper, it is required to call the following code somewhere in
the application (application startup is recommended):
namespace OverrideStyles
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// Create style forwarders
Catel.Windows.Helpers.StyleHelper.CreateStyleForwardersForDefaultStyles();
// Call base
base.OnStartup(e);
}
}
}
Catel.Extensions.CSLA
Catel supports CSLA. All MVVM features in Catel (such as nested user controls) can be used with in combination with the CSLA ViewModelBase.
For more information about CSLA, see http://www.lhotka.net/cslanet/
Catel.Extensions.Data
The data extension provides an implementation of the specification pattern.
Specifications
Specifications
Catel provides an implementation of the . specification pattern
A specification pattern outlines a business rule that is combinable with other business rules. In this pattern, a unit of business logic inherits its
functionality from the abstract aggregate Composite Specification class. The Composite Specification class has one method called IsSatisfiedBy
that returns a boolean value.
After instantiation, the specification is "chained" with other specifications, making new specifications easily maintainable, yet highly customizable
business logic. Furthermore upon instantiation the business logic may, through method invocation or inversion of control, have its state altered in
order to become a delegate of other classes such as a persistence repository.
The big advantage is that commonly used queries can be converted to a specification or a combination of specifications. Then if the query should
change (for example, IsDeleted is introduced), only the specification needs to be changed.
The cool thing about the specification implementation in Catel is that it can be used for Entity Framework queries, but also provides implicit
casting to a Func<TEntity, bool>. This way a specification can be passed to any method accepting a method and thus also works with all linq
queries on normal collections.
Using the classes
Creating a specification is very simple. Below is an example of an active products specification:
public class ActiveProductSpecification : Specification<Product>
{
public ActiveProductSpecification()
: base(x => !x.IsDeleted && x.IsActive)
{
}
}
Then the specification can be used like this:
var productRepository = new ProductRepository();
var activeProductSpecification = new ActiveProductSpecification();
var activeProducts = productRepository.GetQuery(activeProductSpecification);
Catel.Extensions.DynamicObjects
In .NET, it is possible to create fully dynamic objects. This makes it possible to create types of which the members are not yet known at compile
time. Starting with Catel 3.7, the is fully dynamic and still provides the features such as serialization. Catel supports dynamic DynamicModelBase
objects by implementing the which is available in WPF, Silverlight and Windows RT. IDynamicMetaObjectProvider
Creating dynamic objects
Using ModelBase functionality
Supporting serialization of dynamic objects
Creating dynamic objects
Creating a dynamic object with full Catel functionality is easy. Just add the reference via NuGet and create a Catel.Extensions.DynamicObjects
class that derives from : DynamicModelBase
public class DynamicModel : DynamicModelBase
{
// TODO: Add custom functionality if required
}
Then the dynamic model can be used like this:
dynamic model = new DynamicModel();
model.NonExistingProperty = "a dynamic value";
Console.WriteLine(model.NonExistingProperty);
Using ModelBase functionality
The class derives from . However it must be preceded by the keyword. To use the functionali DynamicModelBase ModelBase dynamic ModelBase
ty, cast it to the right type:
For more information about dynamic programming, see MSDN
It is important to know that you must use the keyword to instantiate the type. dynamic
dynamic model = new DynamicModel();
model.NonExistingProperty = "a dynamic value";
Scoping is all done automatically because when a DbContextManager is instantiated, a reference counter is increased. Everytime an instance of
the DbContextManager is disposed, the reference counter is decreased. When the reference count reaches zero (0), it will dispose the DbContext
that it manages.
Sharing a single DbContext per ASP.NET request
When a request is started, a context can be created by calling this code:
DbContextManagerHelper.CreateDbContextForHttpContext<MyEntities>();
When a request is ended, the context can be disposed by using this code:
DbContextManagerHelper.DisposeDbContextForHttpContext<MyEntities>();
Using the repositories and unit of work
The Repository and Unit of Work (UoW) pattern are very useful patterns to create an abstraction level over the DbContext that is provided by
Entity Framework. A much heard excuse not to use repositories is that EF itself already works with repositories (the DbContext) and a UoW (in
the SaveChanges method). Below are a few examples why it is a good thing to create repositories:
Abstract away some of the more complex features of Entity Framework that the end-developer should not be bothered with
Hide the actual DbContext (make it internal) to prevent misuse
Keep security checks and saving and rollback in a single location
Force the use of the Specification pattern on queries
A Unit of Work (UoW) is a a combination of several actions that will be grouped into a transaction. This means that either all actions inside a UoW
are committed or rolled back. The advantage of using a UoW is that multiple save actions to multipleRepositories can be grouped as a unit.
A repository is a class or service responsible for providing objects and allowing end-developers to query data. Instead of querying the DbContext
directly, the DbContext can be abstracted away to provide default queries and force required functionality to all end-developers of the DbContext.
Overview of Unit of Work and repositories
There are different interpretations of how repositories should be used in combination with unit of work. Let's start with an overview how the
DbContext, Repositories and Unit of Work relate to each other. The image below represents an overview of the situation as Catel deals with the
DbContext, Repositories and Unit of Work:
It is very important to wrap the DbContextManager in a using state because it must be disposed
Note that repositories and UoW should not be used to abstract away the ORM tool because that is just another abstraction layer which
is not required. Use it for the advantages mentioned above
The image above shows that the Unit of Work is the top-level component to be used. Each UoW contains its own DbContext instance. The
DbContext can either be injected or will be created on the fly. Then the UoW also contains repositories which always get the DbContext injected.
This way, all repositories inside a UoW share the same DbContext.
Creating a Unit of Work
A UoW can be created by simply instantiating it. The end-developer has the option to either inject the DbContext or let the DbContextManager
take care of it automatically.
using (var uow = new UnitOfWork<MyDbContext>())
{
// get repositories and query away
}
Creating a repository
A repository can be created very easily by deriving from the EntityRepositoryBase class. Below is an example of a customer repository:
public class CustomerRepository : EntityRepositoryBase<Customer, int>,
ICustomerRepository
{
public CustomerRepository(DbContext dbContext)
: base(dbContext)
{
}
}
public interface ICustomerRepository : IEntityRepository<Customer, int>
{
}
1.
2.
Retrieving repositories from a Unit of Work
Once a UoW is created, it can be used to resolve repositories. To retrieve a repository from the UoW, the following conditions must be met:
The container must be registered in the ServiceLocator as Transient type. If the repository is declared as non-transient, it will be
instantiated as new instance anyway.
The repository must have a constructor accepting a DbContext instance
To retrieve a new repository from the UoW, use the following code:
using (var uow = new UnitOfWork<MyDbContext>())
{
var customerRepository = uow.GetRepository<ICustomerRepository>();
// all interaction with the customer repository is applied to the unit of work
}
Saving a Unit of Work
It is very important to save a Unit of Work. Once the Unit of Work gets out of scope (outside the using), all changes will be discarded if not
explicitly saved.
using (var uow = new UnitOfWork<MyDbContext>())
{
var customerRepository = uow.GetRepository<ICustomerRepository>();
// all interaction with the customer repository is applied to the unit of work
uow.SaveChanges();
}
Catel.Extensions.FluentValidation
The validation in Catel is extremely flexible, at this point you must already know it, but sometimes it is just not enough or you are forced to use
external validators.
FluentValidation is a small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your
business objects. Catel provides an extension in order to use FluentValidation as a validation library.
The only thing you have to do is install an isolate package named , available via NuGet, then you will be able to Catel.Extensions.FluentValidation
write your view models validations using FluentValidation approach.
Note that the extension can be used in combination with only, so it is not required to combine it with the FluentValidation Catel.Core
MVVM framework
public class PersonViewModelValidator : AbstractValidator<PersonViewModel>
{
public PersonViewModelValidator()
{
RuleFor(person => person.FirstName).NotEmpty();
RuleFor(person => person.LastName).NotEmpty().WithMessage("Please specify the
last name");
}
}
In order to retrieve the right validators, you must register the : FluentValidatorProvider
ServiceLocator.Default.RegisterType<IValidatorProvider, FluentValidatorProvider>();
The will automatically retrieve the right validators associated with the view models. FluentValidatorProvider
How handle all Catel validation concepts with fluent validation classes?
Catel handle concepts like field or business rules errors and warnings . So, it's necessary map the fluent validation class to the specific Catel
validation using . ValidationDescriptionAttribute
[ValidationDescription(Tag = "Person", ValidationResultType =
ValidationResultType.Error, ValidationType = ValidationType.Field)]
public class PersonViewModelValidator : AbstractValidator<PersonViewModel>
{
}
How FluentValidationProvider works?
FluentValidationProvider is an implementation of IValidationProvider (see: Validation via IValidator). It search all validators classes that
implements FluentValidation.IValidator interface, that also can validate the view model type.
A view model can have one or more validators, so FluentValidationProvider aggregates all validators in a single one using CompositeValidator
class. For performance reasons FluentValidationProvider only searches for validators on the assembly which the view model belongs to.
Catel.Extensions.Interception
Interception Extensions enables you to write code that is executed each time a matching method is invoked using a fluent API. It's suited for cross
cutting concerns, such as transactions, security and logging.
Method interception
Property interception
Interception provides the following advantages:
Strongly-typed syntax
Interception semantics are based on strongly-typed method definitions, which permit to develop aspects taking advantage of features like:
auto-completion, refactoring, compile-time errors, etc;
No configuration
FluentValidationProvider do not use NamingConventions
FluentValidation is only available in NET40, NET45, Silverlight 4 and Silverlight 5
We tried to simplify AOP implementation by favoring convention over configuration. As a result, no configuration of any kind is ever
required to build aspects. However, some conventions take place to make this possible;
Minimum learning-curve
In order to get started, no previous experience with this or any other AOP implementation is required. No need to know or understand
AOP terminology, which is not always very intuitive. By looking at some examples developers can figure out how to intercept calls and
modularize their own applications;
Methods as first-class elements
Utilizing dynamic proxies to implement AOP typically results in having to model aspects as interceptors. Such interceptors are commonly
associated with objects no with methods. Therefore, the developer is responsible for providing the logic to break down object interception
into method interception;
Usage in combination with IoC
The interception mechanism in Catel is based on the registration of a type in the ServiceLocator and in addition of the interception
configuration, this way, you can fully use the advantages of IoC.
Initially, Interception extension support Methods and Properties members for the types you register in the (internally, we create an ServiceLocator
class proxy instance which is actually registered in ). ServiceLocator
Method interception
/// <summary>
/// Register the FirstName property so it is known in the class.
/// </summary>
public static readonly PropertyData FirstNameProperty = RegisterProperty("FirstName",
typeof(string));
/// <summary>
/// Gets or sets the middle name.
/// </summary>
public string MiddleName
{
get { return GetValue<string>(MiddleNameProperty); }
set { SetValue(MiddleNameProperty, value); }
}
/// <summary>
/// Register the MiddleName property so it is known in the class.
/// </summary>
public static readonly PropertyData MiddleNameProperty =
RegisterProperty("MiddleName", typeof(string));
/// <summary>
/// Gets or sets the last name.
/// </summary>
public string LastName
{
get { return GetValue<string>(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
/// <summary>
/// Register the LastName property so it is known in the class.
/// </summary>
public static readonly PropertyData LastNameProperty = RegisterProperty("LastName",
typeof(string));
}
Reference documentation
Reference documentation is available via NuDoc:
http://www.nudoq.org/#/Projects/Catel
Note that it is only possible to use this feature on classes deriving from , such as ModelBase ViewModelBase