Escolar Documentos
Profissional Documentos
Cultura Documentos
5
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.1 Why Catel? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.2 Platform support explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.1.3 Introduction to data objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.1.4 Introduction to MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.1.4.1 MVVM framework comparison sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.1.4.2 Different interpretations of MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.1.4.3 Validation in model or view model? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.1.4.4 Introduction to MVVM and models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.1.4.5 Creating view models with Catel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.1.4.6 Introduction to services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.1.4.7 Introduction to the nested user controls problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.1.4.8 Introduction to unit testing in MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.1.5 Introduction to MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.2 FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.2.1 General . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.2.2 MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
1.3 Setup, deployment and projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.3.1 Getting prerelease (beta) versions via NuGet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.3.2 Stepping through the code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.3.3 Compiling from source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.3.4 Code snippets & templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.3.4.1 Using the code snippets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.3.4.2 Using the item templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.3.4.3 Using the project templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.3.5 Updating to a new version via NuGet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
1.4 Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.4.1 Quick introduction for developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
1.4.2 Getting started with WPF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.4.2.1 Creating the project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
1.4.2.2 Creating the models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.4.2.3 Serializing data from/to disk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
1.4.2.4 Creating the view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
1.4.2.5 Creating the views (user controls) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
1.4.2.6 Creating the views (windows) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
1.4.2.7 Hooking up everything together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
1.4.2.8 Finalizing the application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
1.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
1.5.1 WPF Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
1.5.1.1 Advanced WPF example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
1.5.1.2 Authentication example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
1.5.1.3 Browser application example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
1.5.1.4 Master/detail example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
1.5.1.5 Multilingual example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
1.5.1.6 MVVM communication styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
1.5.1.7 Person application WPF example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
1.5.1.8 Validation example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
1.5.1.9 Viewmodel lifetime example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
1.5.2 Silverlight examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
1.5.2.1 Advanced Silverlight example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
1.5.2.2 Navigation example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
1.5.2.3 Nested user controls example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
1.5.2.4 Person application Silverlight example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
1.5.3 Windows Phone examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
1.5.3.1 Bing maps example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
1.5.3.2 Sensors example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
1.5.3.3 Shopping list example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
1.6 Performance considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
1.7 Catel.Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
1.7.1 Argument checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
1.7.2 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
1.7.3 Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
1.7.4 Data handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
1.7.4.1 ObservableObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
1.7.4.2 DispatcherObservableObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
1.7.4.3 ModelBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
1.7.4.4 Using ModelBase as base for entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
1.7.4.5 Advanced property change notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
1.7.4.6 WCF services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
1.7.5 Exception handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
1.7.6 IoC (ServiceLocator and TypeFactory) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
1.7.6.1 Introductions to IoC components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
1.7.6.1.1 Introduction to the ServiceLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
1.7.6.1.2 Introduction to the TypeFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
1.7.6.1.3 Introduction to DependencyResolver and DependencyResolverManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
1.7.6.1.4 Dependency injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
1.7.6.1.5 Ensuring integrity of the ServiceLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
1.7.6.2 Automatic type registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
1.7.6.2.1 Automatically registering types using attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
1.7.6.2.2 Automatically registering types using conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
1.7.6.3 Setting up the ServiceLocator using configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
1.7.6.4 Replacing the default components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
1.7.7 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
1.7.7.1 Customizing listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
1.7.7.2 Batch log listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
1.7.7.3 Integration with external loggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
1.7.7.3.1 Log4net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
1.7.7.3.2 NLog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
1.7.7.4 Writing logs to disk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
1.7.7.5 Creating log listeners via configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
1.7.7.6 Anotar.Catel.Fody . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
1.7.8 Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
1.7.8.1 MessageBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
1.7.8.2 Message mediator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
1.7.8.3 Messaging via attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
1.7.9 Multilingual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
1.7.10 Parallel invocation and tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
1.7.11 Preventing memory leaks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
1.7.11.1 Change notification wrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
1.7.11.2 Weak events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
1.7.12 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
1.7.13 Scoping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
1.7.14 Serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
1.7.14.1 Introduction to serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
1.7.14.2 Specifying what gets serialized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
1.7.14.3 Customizing serialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
1.7.15 Thread safe code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
1.7.16 Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
1.7.16.1 Validation via validate methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
1.7.16.2 Validation via data annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
1.7.16.3 Validation via special model validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
1.7.16.4 Validation via IValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
1.7.16.5 Using the validation context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
1.7.16.6 Getting a summary of validation results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
1.7.16.7 Deferring validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
1.8 Catel.MVVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
1.8.1 Auditing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
1.8.2 Behaviors & triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
1.8.2.1 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
1.8.2.2 DelayBindingUpdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
1.8.2.3 DoubleClickToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
1.8.2.4 EventToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
1.8.2.5 Focus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
1.8.2.6 FocusFirstControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
1.8.2.7 KeyPressToCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
1.8.2.8 MouseInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
1.8.2.9 Navigate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
1.8.2.10 NumericTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
1.8.2.11 SelectTextOnFocus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
1.8.2.12 UpdateBindingOnPasswordChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
1.8.2.13 UpdateBindingOnTextChanged . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
1.8.3 Commands & events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
1.8.3.1 Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
1.8.3.2 Application-wide commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
1.8.3.3 Asynchronous commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
1.8.3.4 Commands authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
1.8.3.5 Hooking a command to validation automatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
1.8.4 Converters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
1.8.5 Designers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
1.8.6 Handling application initialization parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
1.8.7 Locators and naming conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
1.8.7.1 ViewModelLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
1.8.7.2 ViewLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
1.8.7.3 UrlLocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
1.8.7.4 Naming conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
1.8.8 Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
1.8.8.1 AccelerometerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
1.8.8.2 CameraService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
1.8.8.3 CompassService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
1.8.8.4 GyroscopeService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
1.8.8.5 LocationService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
1.8.8.6 MessageService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
1.8.8.7 NavigationService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
1.8.8.8 OpenFileService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
1.8.8.9 PleaseWaitService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
1.8.8.10 ProcessService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
1.8.8.11 SaveFileService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
1.8.8.12 SchedulerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
1.8.8.13 SelectDirectoryService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
1.8.8.14 SplashScreenService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
1.8.8.15 UIVisualizerService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
1.8.8.16 VibrateService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
1.8.8.17 ViewExportService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
1.8.9 View models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
1.8.9.1 Creating a basic view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
1.8.9.2 Creating a view model that watches over other view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
1.8.9.3 Creating a view model with a model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
1.8.9.4 Creating a view model with a model and mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
1.8.9.5 Mapping properties from view to view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
1.8.9.6 Nested view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
1.8.9.7 Validation in view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
1.8.9.8 Advanced view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
1.8.9.8.1 Keeping view models alive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
1.8.9.8.2 Exposing properties of a model automatically . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
1.8.9.8.3 Determine the view model type dynamically at runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
1.8.9.8.4 Controlling the instantiation of view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
1.8.10 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
1.8.10.1 DataWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
1.8.10.2 UserControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
1.8.10.3 MVVM behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
1.8.10.4 Validation controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
1.8.10.5 Finding the view of a view model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
1.8.10.6 Using external controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
1.8.10.6.1 Using a custom control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
1.8.10.6.2 Using a custom window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
1.8.10.7 Advanced information about views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
1.8.10.7.1 DataWindow - under the hood . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
1.8.10.7.2 UserControl - under the hood . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
1.9 Catel.Mvc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
1.9.1 Configuring dependency injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
1.10 Catel.Extensions.Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
1.10.1 Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
1.10.1.1 StackGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
1.10.1.2 TabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
1.10.1.3 TraceOutputControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
1.10.1.4 WatermarkTextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
1.10.2 Pixel shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
1.10.3 StyleHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
1.10.4 Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
1.11 Catel.Extensions.CSLA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
1.12 Catel.Extensions.Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
1.12.1 Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
1.13 Catel.Extensions.DynamicObjects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
1.14 Catel.Extensions.EntityFramework5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
1.14.1 Using the DbContextManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
1.14.2 Using the repositories and unit of work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
1.15 Catel.Extensions.FluentValidation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
1.16 Catel.Extensions.Interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
1.16.1 Method interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
1.16.2 Property interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
1.17 Catel.Extensions.Memento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
1.17.1 Memento and collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
1.17.2 Memento and methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
1.17.3 Memento and properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
1.18 Catel.Extensions.Prism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
1.18.1 Declaring modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
1.18.2 Translating or customizing the initialization task messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
1.18.3 Using the bootstrapper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
1.19 Catel.Extensions.Wcf.Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
1.20 Catel.Fody . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
1.20.1 Weaving properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
1.20.2 Weaving argument checks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
1.20.3 Exposing properties on view models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
1.20.4 XmlSchema generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
1.21 Catel.ReSharper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
1.21.1 Checking arguments of a method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
1.21.2 Converting regular properties into Catel properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
1.22 Reference documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
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.EntityFramework6
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.8
Catel 3.7
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.Extensions.Wcf.Server
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.
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
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,
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 WPF
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 WPF
Welcome to the guide for Catel and WPF. In this guide, a very simple application will be created with the most commonly used Getting started
aspects of Catel and WPF.
The application will manage family members and will display the families in separate views.
Creating the project
Creating the models
Serializing data from/to disk
Creating the view models
Creating the views (user controls)
Creating the views (windows)
Hooking up everything together
Finalizing the application
Download the final result:WPF.GettingStarted.zip
Note that this guide assumes that the reader has a basic understanding of XAML and WPF since this guide will not cover these basics
Note that this guide will recommend code snippets that can be found . They are not required, just recommended to speed up here
creating Catel classes and properties.
Creating the project
In this step we will create the project and add the relevant NuGet packages.
Creating the project
Adding the NuGet packages
Running the project
Explanation of the project structure
Up next
Creating the project
To create the project, start Visual Studio and choose ... Then switch to the as you can see in the File => New Project on-line template section
screenshot below and search for Catel:
This guide uses the on-line templates that are available in the Visual Studio gallery. If you can't find the templates on-line, please
download them . here
Pick a good name, in our case and click OK. The template will now be downloaded and the project will be created. WPF.GettingStarted
Adding the NuGet packages
As soon as the project is created, the will be opened and instruct your what to do. Right-click on the solution => Readme.txt Manage NuGet
Then search for and click . packages... Catel.Extensions.Controls Install
Running the project
Now the NuGet packages are installed, the project is created and can be built. The basics are created and the application is ready:
Explanation of the project structure
The project template creates the project structure that fits best with Catel. Below is an explanation of the new project structure:
The folder contains the , which contains the logic for the interaction with the view. ViewModels MainWindowViewModel MainWindow
The folder contains the , which represents the actual view. Views MainWindow
Up next
Creating the models
Family = family;
}
Note that the properties are decorated with the attribute which enables the automatic mappings feature in Catel. ViewModelToModel
/// <summary>
/// Gets the family.
/// </summary>
[Model]
public Family Family
{
get { return GetValue<Family>(FamilyProperty); }
private set { SetValue(FamilyProperty, value); }
}
/// <summary>
/// Register the Family property so it is known in the class.
/// </summary>
public static readonly PropertyData FamilyProperty =
RegisterProperty("Family", typeof(Family), null);
/// <summary>
/// Gets the family members.
/// </summary>
[ViewModelToModel("Family")]
public ObservableCollection<Person> Persons
{
get { return GetValue<ObservableCollection<Person>>(PersonsProperty); }
private set { SetValue(PersonsProperty, value); }
}
/// <summary>
/// Register the Persons property so it is known in the class.
/// </summary>
public static readonly PropertyData PersonsProperty =
RegisterProperty("Persons", typeof(ObservableCollection<Person>), null);
/// <summary>
/// Gets or sets the family name.
/// </summary>
[ViewModelToModel("Family")]
public string FamilyName
{
get { return GetValue<string>(FamilyNameProperty); }
set { SetValue(FamilyNameProperty, value); }
}
/// <summary>
/// Register the FamilyName property so it is known in the class.
/// </summary>
public static readonly PropertyData FamilyNameProperty =
RegisterProperty("FamilyName", typeof(string));
}
}
Up next
Creating the views (user controls)
Creating the views (user controls)
In this step we will create the views for the application. There are several views that will be created and both user controls and windows will be
handled in this part of the guide.Catel makes it very easy to create views as user controls with their own view models. In the previous step we
already created the view models.
Person view
Family view
Up next
Person view
To create a new view, right-click the folder in the solution => => => => and search for Catel as you can see in the Views Add New item... On-line
screen below:
Give the new view the name . The view will be added to the folder. PersonView Views
Now we only need to modify the view itself, the code-behind can stay untouched. Since xaml isn't very interesting for this guide, simply copy/paste
the xaml below and set it as content of the view:
Catel will automatically link the and together by naming convention PersonViewModel PersonView
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</catel:StackGrid.ColumnDefinitions>
<Label Content="First name" />
<Label Content="{Binding FirstName}" />
<Label Content="Last name" />
<Label Content="{Binding LastName}" />
</catel:StackGrid>
Family view
The must be created exactly the same way as the . Use the following xaml as content: FamilyView PersonView
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</catel:StackGrid.ColumnDefinitions>
<Label Content="Family name" />
<Label Content="{Binding FamilyName}" />
<Label Grid.ColumnSpan="2" Content="Persons" />
<ItemsControl Grid.ColumnSpan="2" ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:PersonView DataContext="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</catel:StackGrid>
Since this view uses the , it must be defined as a namespace at the top of the file: PersonView
xmlns:views="clr-namespace:WPF.GettingStarted.Views"
The thing that is important to notice in the is how it uses the and injects the models into the data FamilyView PersonView Person PersonView
context.
Up next
Creating the views (windows)
Creating the views (windows)
In this step we will create the windows for the application. In the previous step we already created the user controls.Windows are a great way to
show in an edit-context. Catel provides great edit-windows in the form of the . This is a window that automatically adds and DataWindow OK Canc
buttons (but of course allows customization of the buttons and behavior). el
Person window
Family window
Creating theFamilyWindowViewModel
Creating the FamilyWindow
Up next
Person window
To add a new ,right-click the folder in the solution => => => => and search for Catel as you can see DataWindow Views Add New item... On-line
in the screen below:
Give the new view the name . The view will be added to the folder. PersonWindow Views
Note that we can use the PersonViewModel for both the PersonView (user control) and PersonWindow. Both views represent the same
models and view models, just a different context. To make sure that the IUIVisualizerService knows what view to pick first, register the
PersonWindow in the IUIVisualizerService at application startup:
var uiVisualizerService = serviceLocator.ResolveType<IUIVisualizerService>();
uiVisualizerService.Register(typeof(PersonViewModel), typeof(PersonWindow));
The template will also create a constructor to inject a view model into the window. Please make sure that the constructor takes a view model of
the type instead of the generated . Then replace the content of the view with the xaml below: PersonViewModel PersonWindowModel
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</catel:StackGrid.ColumnDefinitions>
<Label Content="First name" />
<TextBox Text="{Binding FirstName, ValidatesOnDataErrors=True,
NotifyOnValidationError=True}" />
<Label Content="Last name" />
<TextBox Text="{Binding LastName, ValidatesOnDataErrors=True,
NotifyOnValidationError=True}" />
</catel:StackGrid>
Family window
The is a bit different because we want additional logic in this window. We want to create add / edit / remove buttons for the family FamilyWindow
members. Therefore we need to create a separate view model which contains this logic.
Creating theFamilyWindowViewModel
Since the will look a lot like the , just copy/paste the and rename the copy to FamilyWindowViewModel FamilyViewModel FamilyViewModel Family
. WindowViewModel
Creating the FamilyWindow
Once the is created, the must be created exactly the same way as the . Again make sure FamilyWindowViewModel FamilyWindow PersonWindow
to use the right view model ( ) in the constructor of the window in the code-behind. Then use the following xaml: FamilyWindowViewModel
Note that the needs additional logic, but that will be handled in the next part of this getting started guide FamilyWindowViewModel
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</catel:StackGrid.ColumnDefinitions>
<Label Content="Family name" />
<TextBox Text="{Binding FamilyName, NotifyOnValidationError=True,
ValidatesOnDataErrors=True}" />
<Label Grid.ColumnSpan="2" Content="Persons" />
<catel:StackGrid Grid.ColumnSpan="2">
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</catel:StackGrid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Persons}" SelectedItem="{Binding
SelectedPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<views:PersonView DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding AddPerson}" Content="Add..." />
<Button Command="{Binding EditPerson}" Content="Edit..." />
<Button Command="{Binding RemovePerson}" Content="Remove" />
</StackPanel>
</catel:StackGrid>
</catel:StackGrid>
Up next
Hooking up everything together
Hooking up everything together
In this step we will hook everything together and add additional logic to the remaining view models.
Hooking up the view models
Adding additional logic toFamilyWindowViewModel
Adding additional dependencies being injected
Adding the properties
Adding the commands
Adding additional logic to the MainWindowViewModel
Adding additional dependencies being injected
Adding the properties
Adding the commands
Hooking up the views
Up next
Hooking up the view models
We now have most of the application ready. However we need some logic in the view models to hook up everything together.
Adding additional logic toFamilyWindowViewModel
The first thing we are going to do is to finalize the we created in the previous step. To do this, we are going to add a few FamilyWindowViewModel
properties and commands to the view model.
Adding additional dependencies being injected
Since we will be using additional services inside the , it is important to add them as dependency via the constructor. The FamilyWindowViewModel
updated constructor will look like this:
public FamilyWindowViewModel(Family family, IUIVisualizerService uiVisualizerService,
IMessageService messageService)
{
Argument.IsNotNull(() => family);
Argument.IsNotNull(() => uiVisualizerService);
Argument.IsNotNull(() => messageService);
Family = family;
_uiVisualizerService = uiVisualizerService;
_messageService = messageService;
}
Adding the properties
We need a property representing the currently selected person in edit mode of a family. Below is the property definition which needs to be added
to the view model:
/// <summary>
/// Gets or sets the selected person.
/// </summary>
public Person SelectedPerson
{
get { return GetValue<Person>(SelectedPersonProperty); }
set { SetValue(SelectedPersonProperty, value); }
}
/// <summary>
/// Register the SelectedPerson property so it is known in the class.
/// </summary>
public static readonly PropertyData SelectedPersonProperty =
RegisterProperty("SelectedPerson", typeof(Person), null);
Adding the commands
Below is the code which comes in two parts.
1.Add this code to the constructor:
Don't forget to create the right backing fields and _uiVisualizerService _messageService
Note that we recommend that you use the and code snippets available vmcommand vmcommandwithcanexecute here
AddPerson = new Command(OnAddPersonExecute);
EditPerson = new Command(OnEditPersonExecute, OnEditPersonCanExecute);
RemovePerson = new Command(OnRemovePersonExecute, OnRemovePersonCanExecute);
2. Add this code to the view model itself:
/// <summary>
/// Gets the AddPerson command.
/// </summary>
public Command AddPerson { get; private set; }
/// <summary>
/// Method to invoke when the AddPerson command is executed.
/// </summary>
private void OnAddPersonExecute()
{
var person = new Person();
person.LastName = FamilyName;
// Note that we use the type factory here because it will automatically take care
of any dependencies
// that the PersonViewModel will add in the future
var typeFactory = this.GetTypeFactory();
var personViewModel =
typeFactory.CreateInstanceWithParametersAndAutoCompletion<PersonViewModel>(person);
if (_uiVisualizerService.ShowDialog(personViewModel) ?? false)
{
Persons.Add(person);
}
}
/// <summary>
/// Gets the EditPerson command.
/// </summary>
public Command EditPerson { get; private set; }
/// <summary>
/// Method to check whether the EditPerson command can be executed.
/// </summary>
/// <returns><c>true</c> if the command can be executed; otherwise
<c>false</c></returns>
private bool OnEditPersonCanExecute()
{
return SelectedPerson != null;
}
/// <summary>
/// Method to invoke when the EditPerson command is executed.
/// </summary>
private void OnEditPersonExecute()
{
// Note that we use the type factory here because it will automatically take care
of any dependencies
// that the PersonViewModel will add in the future
var typeFactory = this.GetTypeFactory();
var personViewModel =
typeFactory.CreateInstanceWithParametersAndAutoCompletion<PersonViewModel>(SelectedPer
son);
_uiVisualizerService.ShowDialog(personViewModel);
}
/// <summary>
/// Gets the RemovePerson command.
/// </summary>
public Command RemovePerson { get; private set; }
/// <summary>
/// Method to check whether the RemovePerson command can be executed.
/// </summary>
/// <returns><c>true</c> if the command can be executed; otherwise
<c>false</c></returns>
private bool OnRemovePersonCanExecute()
{
return SelectedPerson != null;
}
/// <summary>
/// Method to invoke when the RemovePerson command is executed.
/// </summary>
private void OnRemovePersonExecute()
{
if (_messageService.Show(string.Format("Are you sure you want to delete the person
'{0}'?", SelectedPerson),
"Are you sure?", MessageButton.YesNo, MessageImage.Question) ==
MessageResult.Yes)
{
Persons.Remove(SelectedPerson);
SelectedPerson = null;
}
}
Adding additional logic to the MainWindowViewModel
The same edit functionality we added to the must be added to the . The difference is that FamilyWindowViewModel MainWindowViewModel
instead of adding / editing / removing persons, the will do this for families. MainWindowViewModel
Adding additional dependencies being injected
We will again need additional dependencies. Below is the updated constructor for the : MainWindowViewModel
public MainWindowViewModel(IFamilyService familyService, IUIVisualizerService
uiVisualizerService, IMessageService messageService)
{
Argument.IsNotNull(() => familyService);
Argument.IsNotNull(() => uiVisualizerService);
Argument.IsNotNull(() => messageService);
_familyService = familyService;
_uiVisualizerService = uiVisualizerService;
_messageService = messageService;
}
Adding the properties
We will again need a property to handle the selected family:
/// <summary>
/// Gets or sets the selected family.
/// </summary>
public Family SelectedFamily
{
get { return GetValue<Family>(SelectedFamilyProperty); }
set { SetValue(SelectedFamilyProperty, value); }
}
/// <summary>
/// Register the SelectedFamily property so it is known in the class.
/// </summary>
public static readonly PropertyData SelectedFamilyProperty =
RegisterProperty("SelectedFamily", typeof(Family), null);
Adding the commands
Last but not least, we will also add the commands to the to handle the logic. MainWindowViewModel
1.Add this code to the constructor:
AddFamily = new Command(OnAddFamilyExecute);
EditFamily = new Command(OnEditFamilyExecute, OnEditFamilyCanExecute);
RemoveFamily = new Command(OnRemoveFamilyExecute, OnRemoveFamilyCanExecute);
2. Add this code to the view model itself:
/// <summary>
/// Gets the AddFamily command.
/// </summary>
public Command AddFamily { get; private set; }
/// <summary>
/// Method to invoke when the AddFamily command is executed.
/// </summary>
private void OnAddFamilyExecute()
{
var family = new Family();
// Note that we use the type factory here because it will automatically take care
of any dependencies
// that the FamilyWindowViewModel will add in the future
var typeFactory = this.GetTypeFactory();
var familyWindowViewModel =
typeFactory.CreateInstanceWithParametersAndAutoCompletion<FamilyWindowViewModel>(famil
y);
if (_uiVisualizerService.ShowDialog(familyWindowViewModel) ?? false)
{
Families.Add(family);
}
}
/// <summary>
/// Gets the EditFamily command.
/// </summary>
public Command EditFamily { get; private set; }
/// <summary>
/// Method to check whether the EditFamily command can be executed.
/// </summary>
/// <returns><c>true</c> if the command can be executed; otherwise
<c>false</c></returns>
private bool OnEditFamilyCanExecute()
{
return SelectedFamily != null;
}
/// <summary>
/// Method to invoke when the EditFamily command is executed.
/// </summary>
private void OnEditFamilyExecute()
{
// Note that we use the type factory here because it will automatically take care
of any dependencies
// that the PersonViewModel will add in the future
var typeFactory = this.GetTypeFactory();
var familyWindowViewModel =
typeFactory.CreateInstanceWithParametersAndAutoCompletion<FamilyWindowViewModel>(Selec
tedFamily);
_uiVisualizerService.ShowDialog(familyWindowViewModel);
}
/// <summary>
/// Gets the RemoveFamily command.
/// </summary>
public Command RemoveFamily { get; private set; }
/// <summary>
/// Method to check whether the RemoveFamily command can be executed.
/// </summary>
/// <returns><c>true</c> if the command can be executed; otherwise
<c>false</c></returns>
private bool OnRemoveFamilyCanExecute()
{
return SelectedFamily != null;
}
/// <summary>
/// Method to invoke when the RemoveFamily command is executed.
/// </summary>
private void OnRemoveFamilyExecute()
{
if (_messageService.Show(string.Format("Are you sure you want to delete the family
'{0}'?", SelectedFamily),
"Are you sure?", MessageButton.YesNo, MessageImage.Question) ==
MessageResult.Yes)
{
Families.Remove(SelectedFamily);
SelectedFamily = null;
}
}
Hooking up the views
We now have all the views ready, but we don't see anything yet. The reason for this is that we haven't modified the view yet. To do MainWindow
so, replace the xaml content with the xaml below:
<catel:StackGrid>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</catel:StackGrid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Families}" SelectedItem="{Binding SelectedFamily}">
<ListBox.ItemTemplate>
<DataTemplate>
<views:FamilyView DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding AddFamily}" Content="Add..." />
<Button Command="{Binding EditFamily}" Content="Edit..." />
<Button Command="{Binding RemoveFamily}" Content="Remove" />
</StackPanel>
</catel:StackGrid>
Now run the application and you should see your fully functional family management application.
Up next
Finalizing the application
Finalizing the application
The application we have created so far is fully functional, but misses a bit of the "magic". Below are some additional steps that might make your
application more appealing and more user friendly. Of course you can go as far as you want by creating custom animations and such, but this
guide focuses purely on making the basics more appealing.
Adding validation
Adding behaviors to enable double-click on the list boxes
Adding search functionality to the main window
Adding additional properties to the view model
Adding the search functionality to the view
Adding validation
Adding validation with Catel is extremely easy. There are two flavors to pick from, but they work exactly the same (since both the models and view
models internally derive from ). To add validation to the model, use this code: ModelBase Person
protected override void ValidateFields(List<IFieldValidationResult> validationResults)
{
if (string.IsNullOrWhiteSpace(FirstName))
{
validationResults.Add(FieldValidationResult.CreateError(FirstNameProperty,
"The first name is required"));
}
if (string.IsNullOrWhiteSpace(LastName))
{
validationResults.Add(FieldValidationResult.CreateError(LastNameProperty, "The
last name is required"));
}
}
The validation for the model is very easy as well: Family
protected override void ValidateFields(List<IFieldValidationResult> validationResults)
{
if (string.IsNullOrWhiteSpace(FamilyName))
{
validationResults.Add(FieldValidationResult.CreateError(FamilyNameProperty,
"The family name is required"));
}
}
Adding behaviors to enable double-click on the list boxes
The user must manually click the buttons in the editable views to edit a specific model. To make it easier for the user, we can enable double Edit
click to command behaviors. To do so, navigate to the and add this to the definition: MainWindow ListBox
<ListBox x:Name="listBox" ItemsSource="{Binding Families}" SelectedItem="{Binding
SelectedFamily}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<i:Interaction.Behaviors>
<catel:DoubleClickToCommand Command="{Binding ElementName=listBox,
Path=DataContext.EditFamily}" />
</i:Interaction.Behaviors>
<views:FamilyView DataContext="{Binding}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The same goes for the : FamilyWindow
Note that this validation code can be used in both the model and/or the view models
<ListBox x:Name="listBox" ItemsSource="{Binding Persons}" SelectedItem="{Binding
SelectedPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<i:Interaction.Behaviors>
<catel:DoubleClickToCommand Command="{Binding ElementName=listBox,
Path=DataContext.EditPerson}" />
</i:Interaction.Behaviors>
<views:PersonView DataContext="{Binding}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you have an example application that you want to share, let us know!
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
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 class CustomDependencyPropertySelector : DependencyPropertySelector
{
public override bool MustSubscribeToAllDependencyProperties(Type
targetControlType)
{
return false;
}
}
Then register it in theServiceLocator:
ServiceLocator.Default.RegisterType<IDependencyPropertySelector,
CustomDependencyPropertySelector>();
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
Configuration
Data handling
Exception handling
IoC (ServiceLocator and TypeFactory)
Logging
Messaging
Multilingual
Parallel invocation and tasks
Preventing memory leaks
Reflection
Scoping
Serialization
Thread safe code
Validation
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>(storeNullValues: 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.
}
}
Configuration
Catel makes it very easy to use configurations on all supported platforms.
Getting values from the configuration
Setting values to the configuration
Customizing the way values are stored
Below is a table to explain what technology is used per platform to retrieve and store configuration values.
Platform Technology
.NET ConfigurationManager.AppSettings
Silverlight IsolatedStorageSettings.ApplicationSettings
Windows Phone IsolatedStorageSettings.ApplicationSettings
WinRT ApplicationData.Current.LocalSettings
PCL Not supported
Getting values from the configuration
To retrieve values from the configuration, use the following code:
var configurationService = new ConfigurationService();
var mySetting = configurationService.GetValue<int>("mySetting", 42);
The code above will retrieve the values from the configuration. If the configuration value does not exist, it will return as default value. 42
Setting values to the configuration
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.
It's best to retrieve the service from the dependency resolver or let it be injected into the classes using it
To store values in the configuration, use the following code:
var configurationService = new ConfigurationService();
configurationService.SetValue("mySetting", 42);
Customizing the way values are stored
The is written with extensibility in mind. Though it defaults to the .NET local storage system, it is very easy to create a ConfigurationService
customized configuration service. Below is an example on how to customize the service so it reads and writes values from/to a database.
public class DbConfigurationService : ConfigurationService
{
protected override bool ValueExists(string key)
{
using (var context = new ConfigurationContext())
{
return (from config in context.Configurations
where config.Key == key
select config).Any();
}
}
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
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
[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:
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
Use the retry capabilities
Retry Immediately
Retry defined
Process with retry
Subscribe to the retry events
Handling exceptions manually
Unregistering exceptions
Buffering
Define the way to buffer
Subscribe to the buffering events
Determine if an exception type are registered for handling
Get a specific exception handler
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 occurs, it will show a message FileNotFoundException
to the user. For any other exception, it will log the exception and show a message that the user should contact the developers.
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver .Resolve<IExceptionService>();
exceptionService.Register<FileNotFoundException>(exception =>
dependencyResolver.Resolve<IMessageService>().Show(exception.Message));
exceptionService.Register<Exception>(exception =>
{
Log.Error(exception);
dependencyResolver.Resolve<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 method will handle it and perform the action defined while the registration operation (for Process
example, by showing a message box).
The method comes in two flavors: as action and as function. Process
Executing an action
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.Process(() => { throw new ArgumentOutOfRangeException(); });
Executing a function
The IExceptionService checks for type hierarchy. For example, when an exception as type Exception is registered, this handler will
handle all exceptions
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
var result = exceptionService.Process<int>(() => 1 + 1);
Use the retry capabilities
In some cases, you can want to have possibility to retry an action a certain number of times before finally handle your exception. Let see how the I
allows us to handle this kind of cases. ExceptionService
Firstly, you need to define how the will retry your action in case of error, two possibilities are provided for that. IExceptionService
Retry Immediately
When you setting up your exceptions on , you have to additionnallyuse the method like shown below IExceptionService OnErrorRetryImmediately
:
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.Register<ArgumentNullException>(exception => { /* Do something */ })
.OnErrorRetryImmediately();
This method will say to the to retry the action each times this one throw an exception until it succeed and without to wait before IExceptionService
the next retry.
Retry defined
You have also the possibility to define more deeply the way you want your actions to be retried by using the method like shown OnErrorRetry
below.
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.Register<ArgumentNullException>(exception => { /* Do something */ })
.OnErrorRetry(5, TimeSpan.FromMinutes(2));
Where 5 represents the nombre of times the action will be retried and the interval between theretries. TimeSpan.FromMinutes(2)
Process with retry
If you have provided a retry policy, you can use the method to expect have your policy applied on error. Below an example : ProcessWithRetry
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.ProcessWithRetry(() =>
{
/* Do something */
});
In .NET 4.5, you can process yours actions asynchronously by using the method. ProcessAsync
You can also specify the number of times you want the to retry immediately like this for example : IExceptionService OnErrorRetryImme
diately(5)
Subscribe to the retry events
Can you subscribe to the events which are thown each time an action is retried like this :
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.RetryingAction +=
(sender, eventArgs) =>
Console.WriteLine("The '{0}' have caused retrying action for the
'{1}' times.",
eventArgs.LastException, eventArgs.CurrentRetryCount);
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 dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<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 DivideByZ
) is not registered, the method will rethrow the exception. eroException HandleException
Unregistering exceptions
Although it will probably hardly used, it is possible to unregister exceptions again using the code below:
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.Unregister<ArgumentException>();
Buffering
Define the way to buffer
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 dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.Register<DivideByZeroException>(exception => { })
.UsingTolerance(9, TimeSpan.FromSeconds(10.0));
Here, the idea is to only receive the 10 exception message.
th
Subscribe to the buffering events
Can you subscribe to the events which are thown each time an exception is buffered like this :
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
exceptionService.ExceptionBuffered +=
(sender, eventArgs) =>
Console.WriteLine("The '{0}' is buffered for at '{1}'.",
eventArgs.BufferedException, eventArgs.DateTime);
Determine if an exception type are registered for handling
If you want to know if an exception type have its policy registered on the , you can do this by using the m IExceptionService IsExceptionRegistered
ethod like shown below:
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
if (exceptionService.IsExceptionRegistered<ArgumentNullException>())
{
//Do something
}
Get a specific exception handler
If you want to retrieve the registered exception handler for an exception type, you have to use the method like shown below : GetHandler
var dependencyResolver = this.GetDependencyResolver();
var exceptionService = dependencyResolver.Resolve<IExceptionService>();
var exceptionHandler = exceptionService.GetHandler<ArgumentException>();
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
Also see:
Introductions to IoC components
Automatic type registration
Setting up the ServiceLocator using configuration
Replacing the default components
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
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:
using Catel.IoC; // Contains ObjectExtensions which allow use of below 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();
}
}
Introductions to IoC components
This section contains the introductions.
Introduction to the ServiceLocator
Introduction to the TypeFactory
Introduction to DependencyResolver and DependencyResolverManager
Dependency injection
Ensuring integrity of the ServiceLocator
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 implementing the to gather all services required by Catel. For example, default services are ServiceLocator IServiceLocator
the and the . By default, when the first view model is instantiated, Catel registers all default out of the box IPleaseWaitService IUIVisualizerService
services to the . However, it only does this when the specific services are not already registered. This allows an end-developer to ServiceLocator
register his/her own implementations of the services before any view model is instantiated (for example, at application startup).
The can be instantiated, but Catel instantiates one instance that can be used and shared amongst all objects inside the same ServiceLocator App
. The can be retrieved by using . . Domain ServiceLocator ServiceLocator Default
Registering a type
Use the following code to register a specific type in the : ServiceLocator
ServiceLocator.Default.RegisterType<IPleaseWaitService, PleaseWaitService>();
For more information how types are instantiated and dependency injection, take a look at the TypeFactory documentation
1.
2.
3.
Registering a late-bound type
Use the following code to register a late-bound type in the : ServiceLocator
ServiceLocator.Default.RegisterType<IPleaseWaitService>(x => new PleaseWaitService());
Registering an instance of a type
Catel uses the or to create the interface implementations when the object is first resolved. However, TypeFactory Activator.CreateInstance
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 gives the end-developer a last-resort chance to register a type when it is not registered in the ServiceLocator or any of the ServiceLocator
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:
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>();
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();
}
}
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:
To prevent misuse of the method, it is best to implement the interface explicitly Initialize
The main strategy will be to use the instead of to resolve the types in Catel, starting with version DependencyResolver ServiceLocator
3.8
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
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
DependencyResolverManager.Default = new CustomizedDependencyResolverManager();
Dependency injection
The ServiceLocator in Catel supports dependency injection.
Introduction to dependency injection
Using dependency injection in Catel
Constructor injection
Manually defining the constructor to use for dependency injection
Advanced dependency injection
Property injection
Type is automatically determined based on property type
Type is manually defined
Using tags
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
Note that there is no use to override the as the example, but this keeps it easy to understand DependencyResolverManager
public class MyClass
{
private IFirstDependency _firstDependency;
private ISecondDependency _secondDependency;
public MyClass()
{
_firstDependency = new FirstDependency();
_secondDependency = new SecondDependency();
}
}
[InjectionConstructor]
public MyClass(Person person)
{
// Catel will now first try to use this constructor, no matter the order or number
of parameters
}
Advanced dependency injection
Starting with Catel 3.7, a new type of dependency injection is supported. It allows a developer to instantiate types that combine custom
constructor injection with dependency injection. The class belows shows an interesting combination of custom values that need to be injected and
dependencies that can be retrieved from the IoC container. Before Catel 3.7, one had to manually retrieve the dependencies from the IoC
container when it also required other dependencies to be injected that were not registered in the IoC container:
public class PersonViewModel : ViewModelBase
{
private readonly IMessageService _messageService;
private readonly IProcessService _processService;
The advanced dependency injection can be used by using the class. Below is an example on how to create a new type using TypeFactory
advanced dependency injection:
This feature is initially written to support dependency injection in combination with nested user controls
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
Property injection
Starting with Catel 3.8, it is also possible to use property injection. The difference with constructor injection is that the will TypeFactory
automatically set up all properties that required dependency injection.
To use property injection, simply decorate the properties of a class with the attribute. Below are several options: Inject
Type is automatically determined based on property type
public class MyClass
{
[Inject]
public IMyDependency MyDependency { get; set; }
}
Type is manually defined
public class MyClass
{
[Inject(typeof(IMySubclassedDependency))]
public IMyDependency MyDependency { get; set; }
}
Using tags
It is also possible to determine the tag of a registered dependency:
public class MyClass
{
[Inject(Tag = "myTag")]
public IMyDependency MyDependency { get; set; }
}
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:
Note that the order of the parameters must be the same as the constructor, otherwise the cannot determine the right TypeFactory
constructor to use
Note that the Catel team recommends using constructor injection over property injection because it allows you to check for values null
and store dependencies in private fields
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
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.
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
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
Now you will find the issue in no-time and save yourself a lot of your valuable time!
Automatic type registration
This section contains all the information about automatic type registration.
Automatically registering types using attributes
Automatically registering types using conventions
Automatically registering types using attributes
The in Catel can be set up to discover attribute based registration. ServiceLocator
Declaring a registration since the type definition
There is a way to automatically register types into a service locator. Using it is possible to register types into ServiceLocatorRegistrationAttribute
the service locator in a declarative way. The following code shows how use this attribute:
[ServiceLocatorRegistration(typeof(IMyClass))]
public class MyClass : IMyClass
{
}
All registration options are available in attribute based registration, such as registration type and tag, as const ServiceLocatorRegistrationAttribute
ructor arguments. The following code shows how use such options (it registers the using the interface in a transient way (new MyClass IMyClass
type every time it is resolved) using the tag : MyTag
[ServiceLocatorRegistration(typeof(IMyClass), RegistrationType.Transient, "MyTag")]
public class MyClass : IMyClass
{
}
Activating service locator to scan for automatically registration
By default the service locator doesn't scan for automatic registration. In order to activate this you should set to AutoRegisterTypesViaAttributes tru
. e
var serviceLocator = ServiceLocator.Default;
serviceLocator.AutoRegisterTypesViaAttributes = true;
Automatically registering types using conventions
The in Catel can be set up to automatically register types based on the conventions. ServiceLocator
Register using Naming Convention
Register using FirstInterfaceConvention
Filter types to register
Exclude all types of the namespace containing the specified type
Exclude a specific type
Exclude types using predicate
Some conventions are provided by default to allow us to register types.
Register using Naming Convention
You can want to register all types which match with the default naming convention, means when I have a interface, I expect the IService
convention to find and register the class as implementation. Service
To discover types for naming convention registration, we have to simply do :
var serviceLocator = new ServiceLocator();
serviceLocator.RegisterTypesUsingDefaultNamingConvention();
Register using FirstInterfaceConvention
You can want to register all types which match with the default first interface convention, means when I have a class which implements Service
more than one interface, I expect the convention to find the first interface and use it as service registration.
To discover types for first interface convention registration, we have to simply do :
var serviceLocator = new ServiceLocator();
serviceLocator.RegisterTypesUsingDefaultFirstInterfaceConvention();
Filter types to register
You have the ability to apply filtering on the registration process to exclude for example or include some types.
Exclude all types of the namespace containing the specified type
If you want to exclude all types of the namespace which belong to a particular type, just do that:
var serviceLocator = new ServiceLocator();
serviceLocator.RegisterTypesUsingDefaultNamingConvention()
.ExcludeAllTypesOfNamespaceContaining<IFooService>();
Here, we say to the to ignore all types included into the namespace which belong to the type. ServiceLocator IFooService
The convention based registration should to be run first than the others registration methods to be sure to have all your types registered
correctly.
Exclude a specific type
If you want to exclude a specific type, you can do that by using the method like shown below: ExcludeType
var serviceLocator = new ServiceLocator();
serviceLocator.RegisterTypesUsingDefaultNamingConvention()
.ExcludeType<IFooService>();
The will be exclude on the registration process. IFooService
Exclude types using predicate
You also have the possibility to filter types by using a predicate, below an example :
var serviceLocator = new ServiceLocator();
serviceLocator.RegisterTypesUsingDefaultNamingConvention()
.ExcludeTypesWhere(type => type == typeof(IFooService));
To configure a service locator from the default service locator configuration use the following code:
Catel.IoC.IoCConfiguration.UpdateDefaultComponents();
At this moment, Catel will fully replace the components (in this case the and ), but will keep using the default IServiceLocator ITypeFactory
implementation of the . IDependencyResolver
Creating IoC components in code
It is best to respect the customization of the IoC components in the code. Therefore it is wise to always use the to create a IoCFactory ServiceLoc
when a is needed: ator new instance
var serviceLocator = IoCFactory.CreateServiceLocator();
Catel will automatically create the right and and register them in the newly created . IDependencyResolver ITypeFactory IServiceLocator
Logging
Starting with version 2.2, Catel uses a custom internal logging system. This way, the reference to log4net could be removed. The idea behind this
move is not to force the user to use log4net. Also, log4net seems deprecated (no new releases for a long time), and other logging systems such
as NLog seem to grow in popularity.
The new logging system only allows very basic logging. This way, the logging is kept very simple and the real logging can be implemented by the
end-developer if he/she feels the need for it.
Table of contents
Log and ILog
LogManager
Logging in code
Logging to the output window or console
Overriding global log level flags
Other info
Customizing listeners
Batch log listeners
Integration with external loggers
Writing logs to disk
Creating log listeners via configuration
Anotar.Catel.Fody
Log and ILog
All logging is available via the ILog interface. This interface is registered automatically on all objects in Catel as Log field. This way, every object
can log any information by calling methods on the Log field.
In Catel, there is only one implementation of the ILog interface, which is the Log class. This class makes sure that the log messages are formatted
correctly and the LogMessage event is invoked when a message is written to the log.
Catel internally creates a separate log per type. This way, there will be lots of logs and it should be easy to filter the information the end-developer
is really interested in.
LogManager
The LogManager is the class where it all comes together. This class is responsible for creating new logs for types, but also keeps track of all logs
Note that the customization of the IoCConfiguration is the thing that must be done at application start up first
and log listeners. To retrieve the log for a specific class, use the following code:
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
Logging in code
To log in code, the ILog interface implements some basic methods to log information with an option for extra data. There are however lots of
extension methods available to log exceptions, string formats and more. Below is an example of logging in code:
Log.Warning("Customer '{0}' does not exist", customerId);
Or, if an exception is available, this can written to the log as well.
Log.Error(ex, "Failed to delete file '{0}'", fileName);
Logging to the output window or console
By default, Catel does not add any listeners. However, it contains a ready-to-use implementation that writes all logs to the output window or
console, which is the DebugLogListener. To register this listener, call this at any time:
#if DEBUG
LogManager.AddDebugListener();
#endif
Overriding global log level flags
Start with Catel 3.8, it is possible to override the global log level flags for all listeners. To do this, set the corresponding flag on the to LogManager
a value. For example, to force debug logging on all log listeners, use the code below:
LogManager.IsDebugEnabled = true;
To reset the override, set the value back to : null
LogManager.IsDebugEnabled = null;
Customizing listeners
Each listener can be customized to only receive the logs that the listener is interested in. This way, the listener does not receive events it is not
interested in. For example, to only receive errors, create a new listener and use the following customization:
Prior to Catel 3.8, one should use instead LogManager.RegisterDebugListener()
1.
2.
3.
var listener = new MyLogListener();
listener.IsDebugEnabled = false;
listener.IsInfoEnabled = false;
listener.IsWarningEnabled = false;
listener.IsErrorEnabled = true;
By default, all types of logging are enabled on a log listener.
Batch log listeners
Flushing all listeners
Implementing a custom IBatchLogListener
A batch log listener is a class implementing the IBatchLogListener interface (and most probably deriving from BatchLogListenerBase). This
interface adds a Flush method which allows a listener to be flushed. The advantage is that when a log listener writes to a slower persistence
store, it will not have to access this expensive resource for every log event, but by batches.
Flushing all listeners
When using batch log listeners, it is very important to flush the log listeners at important events such as application unhandled exceptions or when
the application exits. The reason is that otherwise important log events that are currently in the batch that hasn't been written to the persistence
store are lost.
To flush all flushable listeners, use the following method:
LogManager.FlushAll();
Implementing a custom IBatchLogListener
When implementing a custom batch log listener, it is very wise to derive from the class. This brings the following BatchLogListenerBase
advantages:
The is thread-safe BatchLogListenerBase
The automatically flushes the listener every 5 seconds BatchLogListenerBase
You only need to implement the which actually writes the entries to the persistence store WriteBatch
Below is an example batch log listener:
public class FileLogListener : BatchLogListenerBase
{
private readonly string _filePath;
private readonly int _maxSizeInKiloBytes;
public FileLogListener(string filePath, int maxSizeInKiloBytes)
{
Argument.IsNotNullOrWhitespace(() => filePath);
_filePath = filePath;
_maxSizeInKiloBytes = maxSizeInKiloBytes;
}
protected override void WriteBatch(System.Collections.Generic.List<LogBatchEntry>
batchEntries)
{
try
{
var fileInfo = new FileInfo(_filePath);
if (fileInfo.Exists && (fileInfo.Length / 1024 >= _maxSizeInKiloBytes))
{
CreateCopyOfCurrentLogFile(_filePath);
}
using (var fileStream = new FileStream(_filePath, FileMode.Append,
FileAccess.Write, FileShare.Read))
{
using (var writer = new StreamWriter(fileStream))
{
foreach (var batchEntry in batchEntries)
{
var message = FormatLogEvent(batchEntry.Log, batchEntry.Message,
batchEntry.LogEvent, batchEntry.ExtraData);
writer.WriteLine(message);
}
}
}
}
catch (Exception)
{
// Swallow
}
}
private void CreateCopyOfCurrentLogFile(string filePath)
{
for (int i = 1; i < 999; i++)
{
var possibleFilePath = string.Format("{0}.{1:000}", filePath, i);
if (!File.Exists(possibleFilePath))
{
File.Move(filePath, possibleFilePath);
}
}
}
}
Integration with external loggers
The logging in Catel does not write any output by default. This gives a developer freedom to use any final logging mechanism in the way he or
she intended. For example, Catel can easily be integrated with log4net or NLog. Basically, the following steps are required to implement an
1.
2.
1.
2.
3.
external log solution:
Create a custom ILogListener by creating your own or using the LogListenerBase implementation
Register it in the LogManager using the LogManager.AddListener
Log4net
NLog
Log4net
The example below provides an ILogListener for NLog, but any external logging library can be used.
Creating the listener
A listener can be created by creating a new class deriving from LogListenerBase.
public class Log4netListener : LogListenerBase
{
protected override void Debug(ILog log, string message, object extraData)
{
var finalLog = log4net.LogManager.GetLogger(log.TargetType);
finalLog.Debug(message);
}
protected override void Info(ILog log, string message, object extraData)
{
var finalLog = log4net.LogManager.GetLogger(log.TargetType);
finalLog.Info(message);
}
protected override void Warning(ILog log, string message, object extraData)
{
var finalLog = log4net.LogManager.GetLogger(log.TargetType);
finalLog.Warn(message);
}
protected override void Error(ILog log, string message, object extraData)
{
var finalLog = log4net.LogManager.GetLogger(log.TargetType);
finalLog.Error(message);
}
}
Registering the listener
Last but not least, it is important to register the listener:
LogManager.AddListener(new Log4netListener());
Configuring log4net
Add reference to log4net
Add [assembly: log4net.Config.XmlConfigurator(Watch = true)] to AssemblyInfo.cs
Configure log4net in your app.config to configure the actual data
NLog
Note that this is just a sample configuration. Please use the log4net documentation for all options
The example below provides an ILogListener for NLog, but any external logging library can be used.
Creating the listener
A listener can be created by creating a new class deriving from LogListenerBase.
public class NLogListener : LogListenerBase
{
protected override void Debug(ILog log, string message, object extraData)
{
var finalLog = NLog.LogManager.GetLogger(log.TargetType.ToString());
finalLog.Debug(message);
}
protected override void Info(ILog log, string message, object extraData)
{
var finalLog = NLog.LogManager.GetLogger(log.TargetType.ToString());
finalLog.Info(message);
}
protected override void Warning(ILog log, string message, object extraData)
{
var finalLog = NLog.LogManager.GetLogger(log.TargetType.ToString());
finalLog.Warn(message);
}
protected override void Error(ILog log, string message, object extraData)
{
var finalLog = NLog.LogManager.GetLogger(log.TargetType.ToString());
finalLog.Error(message);
}
}
Registering the listener
Last but not least, it is important to register the listener:
LogManager.AddListener(new NLogListener());
Writing logs to disk
Catel also supports very lightweight listeners to allow external logging libraries to hook on. To create a listener, first create a new class that
implements the interface. Next, register it in the using the method. ILogListener LogManager LogManager.AddListener
The has a separate method for each , but also has a shared method that is called for each log event. For example, if a ILogListener LogEvent
debug message is written to the log, both the Write and Debug methods are invoked on the . ILogListener
Creating the listener
A listener can be created by creating a new class deriving from . LogListenerBase
For an example which writes to disk in batches, see the batch log event listeners
Note that Catel already contains a FileLogListener and there is no need to reproduce this class. It only acts as an example that is easy
to understand
public class FileLogListener : LogListenerBase
{
private readonly TextWriter _textWriter;
public FileLogListener(string fileName)
{
Argument.IsNotNullOrWhitespace("fileName", fileName);
FileName = fileName;
_textWriter = new StreamWriter(fileName, true);
}
public string FileName { get; private set; }
public override void Write(ILog log, string message, LogEvent logEvent)
{
_textWriter.WriteLine(message);
}
}
Registering the listener
Last but not least, it is important to register the listener:
LogManager.AddListener(new FileLogListener("<log_file_path>"));
Creating log listeners via configuration
Starting with Catel 3.8, it is possible to instantiate classes from the configuration. Below is an example on how to customize the LogListener
listeners:
<configSections>
<sectionGroup name="catel">
<section name="logging" type="Catel.Logging.LoggingConfigurationSection, Catel.Core"
/>
</sectionGroup>
</configSections>
<catel>
<logging>
<listeners>
<listener type="Catel.Logging.FileLogListener" FilePath="CatelLogging.txt"
IgnoreCatelLogging="true"
IsDebugEnabled="false" IsInfoEnabled="true" IsWarningEnabled="true"
IsErrorEnabled="true"/>
</listeners>
</logging>
</catel>
It is important to register the section as shown in the example above. Then the section in the bottom can contain an unlimited logging logging
number of listeners. Each listener has to provide at least the property which contains the type and namespace of the which type ILogListener
must be added:
<listener type="Catel.Logging.FileLogListener" />
The other properties are fully customizable and can be defined on the fly. This means that the configuration is fully customizable for every listener
that needs to be added. Below is an example to register the via configuration: FileLogListener
<listener type="Catel.Logging.FileLogListener" FilePath="CatelLogging.txt"
IgnoreCatelLogging="true"
IsDebugEnabled="false" IsInfoEnabled="true" IsWarningEnabled="true"
IsErrorEnabled="true"/>
The properties ( , , and ) are available to all listeners. All other ILogListener IsDebugEnabled IsInfoEnabled IsWarningEnabled IsErrorEnabled
properties depend on the the actual listener being registered. This allows major flexibility at runtime.
Anotar.Catel.Fody
Logging is a very important part of an application. It provides detailed information about the application workflow, even when an application is
already deployed to several clients. Thats the reason that logging is a first class citizen in the Catel framework.
In general, logging works by defining an instance on a class: ILog
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
Then in any method, logging can be added like this:
Log.Info("This is a logging with a format '{0}'", "test");
Writing the definition can be boring and repetitive. Luckily came up with a solution for that, namely . With the Log Simon Cropp Anotar.Catel.Fody
Anotar implementation, a reference will be added to the solution. Then after compilation the assembly will be removed and all calls to the cl LogTo
ass will be replaced by actual calls to the Catellogging classes.
How to use Anotar
Using Anotar is really easy, just call the static methods on the class as you can see below: LogTo
LogTo.Info("This is a logging with a format '{0}'", "test");
Note that it is no longer required to define the field, it will be added automatically by Anotar. Log
Besides that it is really easy to use, another benefit is a very small performance improvement. The uses reflection to GetCurrentClassLogger
determine the current class. This is a very slight hit on performance the first time a class is used (only once, the field is static). Anotar directly
replaces the call by an implementation like this:
private static readonly ILog Log = LogManager.GetLogger(typeof(MyClass));
Additional options
Disabling method names and line numbers
By default Anotar also logs the method and line number:
03:58:11:858 => [DEBUG] [AnotarDemo.Program] Method: 'Void Main(String[])'. Line: ~19.
this is a test
If you don't want such output, add this attribute on assembly level:
[assembly: LogMinimalMessage]
Then the output will look like this:
03:59:36:344 => [DEBUG] [AnotarDemo1.Program] this is a test
Logging exceptions automatically
It is possible to automatically log exceptions inside a method. To accomplish this, decorate the method with the attri LogTo[LogLevel]OnException
bute:
[LogToDebugOnException]
public static void ExceptionalMethod()
{
throw new Exception("This will be logged automatically");
}
Then the output will be as follows:
04:01:48:331 => [DEBUG] [AnotarDemo.Program] Exception occurred in 'Void
ExceptionalMethod()'. | [Exception] System.Exception: This will be logged
automatically
at AnotarDemo.Program.ExceptionalMethod() in
c:\Source\AnotarDemo\AnotarDemo\Program.cs:line 27
Messaging
MessageBase
Message mediator
Messaging via attributes
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 dependencyResolver = this.GetDependencyResolver();
var mediator = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var mediator = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<IMessageService>();
messageService.Show(value);
}
Multilingual
Making an application multilingual is a very common feature request nowadays. Therefore Catel provides the resources in several languages and
provides the to give the developers full control over the translation process in their applications. LanguageService
Setting up the LanguageService
Setting cultures
Registering custom language sources
Using the LanguageService
Using the LanguageService in XAML
Using theLanguageBinding in WPF and Silverlight
Using the LanguageBinding in Windows Phone
Implementing custom LanguageService (from database)
Creating a custom ILanguageSource implementation
Creating a custom DbLanguageService
Enabling the custom DbLanguageService
Setting up the LanguageService
Setting cultures
By default the will use the current UI culture to retrieve the right language values. These can easily be customized: LanguageService
var dependencyResolver = this.GetDependencyResolver();
var languageService = dependencyResolver.Resolve<ILanguageService>();
// Create source for assembly MyApplication where the Resources.resx is located in the
Properties folder
var resourcesSource = new LanguageResourceSource("MyApplication",
"MyApplication.Properties", "Resources");
languageService.RegisterLanguageSource(resourcesSource );
serviceLocator.RegisterInstance<ILanguageService>(dbLanguageService);
Parallel invocation and tasks
This page contains helper classes in Catel to invoke specific actions on very large collections in parallel.
Running batches on large sets in parallel
Running batches on large sets in parallel
When handling a lot of items and invoking a method per item, it might be a viable option to execute the actions in batches. This normally requires
quite some code to split up the large collection into batches and execute the method for each item. To make this process much easier, Catel
introduces the class. ParallelHelper
To invoke an method on all types currently loaded by Catel, in batches of 2500 types per batch, use the following code: Initialize
var allTypes = new List<Type>(TypeCache.GetTypes());
ParallelHelper.ExecuteInParallel(allTypes, type =>
{
SomeInitializeTypeMethod(type);
}, 2500, "Initialize types");
It is really easy to tweak the number of items per batch to find the optimal performance of items per batch.
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:
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
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.
1.
2.
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();
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");
Scoping
Sometimes scoping is important to share an object inside a specific scope which cannot be determined upfront. A great example is the
serialization inside Catel which requires a serialization scope which can be shared over a lot of objects. Scoping in Catel is really. To create a
scope of an object with a specific tag, use the code below:
using (var scopeManager = ScopeManager<object>.GetScopeManager("object"))
{
var scopeObject = scopeManager.ScopeObject;
[ExcludeFromSerialization]
public string CatelProperty
{
get { return GetValue<string>(CatelPropertyProperty); }
set { SetValue(CatelPropertyProperty, value); }
}
public static readonly PropertyData CatelPropertyProperty =
RegisterProperty("CatelProperty", typeof(string), null);
}
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 documentation engine has been completely rewritten for Catel 3.7. This documentation only applies to Catel 3.7 and higher.
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
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
Workflow 1 represents the serialization. Workflow 2 represents the deserialization.
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"));
}
}
SuspendValidation = false;
_messageService = messageService;
// Add commands
MyAction = new Command(MyAction_Execute);
}
/// <summary>
/// Gets the MyAction command.
/// </summary>
public Command MyAction { get; private set; }
/// <summary>
/// Method to invoke when the MyAction command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void MyAction_Execute(object parameter)
{
// Show message box
_messageService.ShowInfo("My action in MVVM");
}
Xaml (assuming that the view model is set as datacontext):
<Button Content="Click me" Command="{Binding MyCommand}" />
Application-wide commands
Most commands are registered per view and available per view model. Some commands (such as commands on a or ) are Ribbon Toolbar
application-wide. Catel supports both types, and this part of the documentation explains how to use the to work with ICommandManager
application-wide commands such as with a key bound to . Refresh F5
Introduction to CommandManager
Creating application-wide commands
Registering a custom command
Using application-wide commands in xaml
Introduction to CommandManager
There is no generic way to specify application-wide commands in WPF and Silverlight. To overcome this issue, Catel introduces the CommandMa
. This manager allows to create commands which are hosted by the . The commands on the command manager can be nager CommandManager
created with input gestures (on both WPF and Silverlight). Once a view model wants to hook into a specific command, it only has to register the
view model command with the application-wide command.
Creating application-wide commands
To create application-wide commands, one must resolve the from the and create the command: ICommandManager DependencyResolver
var dependencyResolver = IoCConfiguration.DefaultDependencyResolver;
var commandManager = dependencyResolver.Resolve<ICommandManager>();
Asynchronous commands
Commands in MVVM are a very precious good. Actually, MVVM can't exist without them because they allow a developer to bind to a method
(that's actually all an implementation is). However, sometimes it is required to create asynchronous commands. Starting with Catel ICommand
3.1, the is introduced. AsynchronousCommand
With the , it is possible to create a command that executes a method in the background without blocking the UI thread. It AsynchronousCommand
is possible to report progress to the UI thread.
Creating the command
The can be used the same as the the regular class. The command must be constructed like this: AsynchronousCommand Command
CancelableAsyncCommand = new AsynchronousCommand(OnCancelableAsyncCommand, () =>
!CancelableAsyncCommand.IsExecuting);
The second parameter is the delegate, and this example does not allow multiple executions of the same command at the same time. CanExecute
Running the command
Running the command is the same as the regular class. The view can simply bind to the command like the code below: Command
<Button Command="{Binding CancelableAsyncCommand}" Content="Execute Command" />
Canceling the command
Last but not least, the cool thing about the is that it can complete in two ways. It either completes the method by itself, or AsynchronousCommand
it is canceled from the outside. To cancel a running command, two steps must be accomplished:
1.Bind to the property of the : CancelCommand AsynchronousCommand
<Button Command="{Binding CancelableAsyncCommand.CancelCommand}" Content="Cancel
Command" />
2.Make sure the execute action checks the property like in the example below: ShouldCancel
private void OnCancelableAsyncCommand()
{
for (var i = 1; i <= 100; i++)
{
if (CancelableAsyncCommand.ShouldCancel)
{
// If we should cancel, break out of the loop
break;
}
Thread.Sleep(100);
}
}
Reporting progress
During the execution of a command, it is possible to report progress back to the main thread. This can done by using the method. ReportProgress
All code inside the will be executed in the UI thread: ReportProgress
Note that this example looks stupid and you should not use Thread.Sleep, but this is just for the sake of simplicity and the example
private void OnCancelableAsyncCommand()
{
for (var i = 1; i <= 100; i++)
{
if (CancelableAsyncCommand.ShouldCancel)
{
// If we should cancel, break out of the loop
break;
}
var i1 = i;
CancelableAsyncCommand.ReportProgress(() =>
Messages.Add(i1.ToString(CultureInfo.InvariantCulture)));
Thread.Sleep(100);
}
}
Commands authentication
One of the questions an MVVM developer faces is how to control the executation state of a command by role or user authentication method. Catel
offers an out-of-the-box solution for this problem to check the state of the commands in the UI. CanExecute
Tagging your commands
To know whether a specific user can execute a command, you need to be able to distinguish one command from another. The in ICatelCommand
terface (which derives from ) providers a property that allows you to tag the command with any object that fits your needs. In one ICommand Tag
application, commands might be distinguished using strings, other applications use integer ID's.
A tag must be set in the constructor of a command and cannot be changed:
Edit = new Command(OnEditExecute, OnEditCanExecute, "editCommand");
IAuthenticationProvider
The is a provider that needs to be implemented per application and must be registered in the IoC container. Below is the IAuthenticationProvider
interface definition:
It is very important that this way of disabling commands is only used to easy the development of consistent user interfaces. It cannot
replace the actual check whether a user can or cannot modify data. The actual and final responsibility still lays at the business layer.
/// <summary>
/// Interface to allow an authentication mechanism to control the CanExecute state of
a command.
/// </summary>
public interface IAuthenticationProvider
{
/// <summary>
/// Determines whether the specified <paramref name="command"/> can be executed.
The class implementing this interface
/// can use any required method to check the command.
/// <para />
/// It is recommended to use the <see cref="ICatelCommand.Tag"/> property to
identify a command.
/// </summary>
/// <param name="command">The command that is requested.</param>
/// <param name="commandParameter">The command parameter.</param>
/// <returns>
/// <c>true</c> if this instance [can command be executed] the specified
command; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// The <c>CanExecute</c> state of a command is queried a lot. The command itself
does not cache any results because
/// it is not aware of role or identity changes. If caching is required, this must
be implemented in the class implementing
/// the <see cref="ICommandAuthenticationProvider"/> interface.
/// </remarks>
bool CanCommandBeExecuted(ICatelCommand command, object commandParameter);
}
To register a custom implementation of the command authentication provider, use the code below:
Catel.IoC.ServiceLocator.Instance.RegisterType<IAuthenticationProvider,
RoleAuthenticationProvider>();
The code above registers a custom made command authentication provider that checks whether a specific role can execute the command.
Hooking a command to validation automatically
It is possible to hook the of a to the automatically. This way, there is no need to check for errors CanExecute Command IValidationSummary
manually in the method. The example below first adds a validation summary to a view model to get the validation result. Then, it uses CanExecute
this validation summary to automatically determine whether a command can be executed.
1.Add validation to a person view model (note how the validation adds the tag to a validation): PersonValidation
Catel checks whether an is registered. If not, the way commands are handled is not affected in any way. If there IAuthenticationProvider
is an available, the state is checked, even when there is no custom implemented. IAuthenticationProvider CanExecute CanExecute
/// <summary>
/// Validates the field values of this object. Override this method to enable
/// validation of field values.
/// </summary>
/// <param name="validationResults">The validation results, add additional results to
this list.</param>
protected override void
ValidateFields(System.Collections.Generic.List<IFieldValidationResult>
validationResults)
{
if (string.IsNullOrEmpty(FirstName))
{
validationResults.Add(FieldValidationResult.CreateErrorWithTag(FirstNameProperty,
"First name cannot be empty", "PersonValidation"));
}
if (string.IsNullOrEmpty(LastName))
{
validationResults.Add(FieldValidationResult.CreateErrorWithTag(LastNameProperty, "Last
name cannot be empty", "PersonValidation"));
}
}
2.Add a property to the view model containing the validation summary using the attribute. ValidationToViewModel
[ValidationToViewModel(Tag = "PersonValidation")]
public IValidationSummary PersonValidationSummary { get; set; }
3. Define a command on the view model:
/// <summary>
/// Gets the Save command.
/// </summary>
public Command Save { get; private set; }
/// <summary>
/// Method to invoke when the Save command is executed.
/// </summary>
private void OnSaveExecute()
{
// TODO: Handle command logic here
}
4. Create the command that automatically uses the validation summary using the class: CommandHelper
Save = CommandHelper.CreateCommand(OnSaveExecute, () => PersonValidationSummary);
With this example, the command on the view model can only be executed when there are no errors with the tag. Save PersonValidation
Converters
In MVVM, there will be some point where you will need to use converters. Most of these converters are used in any project, so we have decided
to add them to Catel. Below is a list of converters and a short description what they are used for.
Linking converters
Available converters
Linking converters
It is possible to link converters. To link converters, simply set the property in xaml: Link
<catel:NullableValueConverter x:Key="NullableValueConverter" />
<catel:BooleanToVisibilityConverter>
<catel:BooleanToVisibilityConverter.Link>
<code:NullToBoolConverter Link="{StaticResource NullableValueConverter}" />
</catel:BooleanToVisibilityConverter.Link>
</catel:BooleanToVisibilityConverter>
Available converters
Name Description
AreEqualMultiValueConverter Converts the comparison of 2 values to a boolean
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 " ".
BooleanToCollapsingVisibilityConverter Convert from bool to and back. True returns , Visibility Visibility.Visible
False returns . Visibility.Collapsed
BooleanToHidingVisibilityConverter Convert from bool to and back. True returns , Visibility Visibility.Visible
False returns . Visibility.Hidden
ColorToBrushConverter Converts a color value to a brush and vice versa.
ContainsItemsConverter Convert the count of an ICollection or IEnumerable to true or false,
depending on whether the instance contains items.
For an instance which implements ICollection, check Count > 0
For an instance which implements IEnumerable, if the instance can
be Enumerated.
CountCollapsedConverter Converts the 'count' of ICollection, string, long, int or short to Visibility
or .Visible Visibility.Collapsed
Visible means: ICollection.Count > 0, String.Length > 0 or long, int,
short > 0.
CountHiddenConverter Converts the 'count' of ICollection, string, long, int or short to Visibility
or .Visible Visibility.Hidden
Visible means: ICollection.Count > 0, String.Length > 0 or long, int,
short > 0.
Note that the behavior of most converters can be inverted by using the ConverterParameter
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.
FormattingConverter Formats the value using the format string provided in the
ConverterParameter
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.
GetFirstValidationErrorConverter Converts a collection containing ValidationError objects to return the
first error or an empty string in case there are no errors.
IsSelectedConverter Converts a selected value to either true or false. Useful whena
mutually exclusive selection must be made.
IntToStringConverter Converts an inteteger to a string and back.
MethodToValueConverter Converts the result of a method to a value. This makes it possible to
bind to a method. See Source
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
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:
/// <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:
Although the is not available in Windows Phone, it still shows how to add design time support DataWindow
<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
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
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
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
. 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
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
[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
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
For more information about naming conventions, see Naming conventions
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));
For more information about naming conventions, see Naming conventions
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:
/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
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
/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");
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
For more information about naming conventions, see Naming conventions
[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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var accelerometerService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var accelerometerService = dependencyResolver.Resolve<IAccelerometerService>();
accelerometerService.CurrentValueChanged += OnAccelerometerValueChanged;
accelerometerService.Start();
Stopping the service
It is important that the service must be started and stopped to retrieve values
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 dependencyResolver = this.GetDependencyResolver();
var accelerometerService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var cameraService = dependencyResolver.Resolve<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
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
protected override void Close()
{
var dependencyResolver = this.GetDependencyResolver();
var cameraService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var compassService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var compassService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var compassService = dependencyResolver.Resolve<ICompassService>();
compassService.CurrentValueChanged -= OnCompassValueChanged;
compassService.Stop();
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
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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var gyroscopeService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var gyroscopeService = dependencyResolver.Resolve<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:
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
var dependencyResolver = this.GetDependencyResolver();
var gyroscopeService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Starting the service
The GPS service needs to be started and stopped. To start the GPS service, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var locationService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var locationService = dependencyResolver.Resolve<ILocationService>();
locationService.LocationChanged -= OnCurrentLocationChanged;
locationService.Stop();
Emulating GPS without device
It is important that the service must be started and stopped to retrieve values
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
var dependencyResolver = this.GetDependencyResolver();
Test.LocationService service =
(Test.LocationService)dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Screenshot
Showing a message
To show a message from a view model, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var messageService = dependencyResolver.Resolve<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
For WPF and Silverlight, the pages must inherit from . For WP7, the pages must inherit from . In WPF, the Page PhoneApplicationPage
Platform info
Framework Supported
WPF
Silverlight 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Closing an application
It is possible to close an application using the following code:
var dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<INavigationService>();
navigationService.CloseApplication();
Preventing an application to be closed
To prevent an application to be closed, one can subscribe to the event: ApplicationClosing
var dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<INavigationService>();
navigationService.ApplicationClosing += (sender, e)
=>
{
e.Cancel = true;
};
Navigating to a new view
To navigate to a new page, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<INavigationService>();
navigationService.Navigate<EmployeeViewModel>();
Navigating with parameters
It is very easy to navigate to a new page with parameters. Use the following code:
parameters are of type , in Silverlight and WP7, the arguments are of type . Dictionary<string, object> Dictionary<string, string>
var parameters = new Dictionary<string, object>();
parameters.Add("id", employee.EmployeeID);
var dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<INavigationService>();
navigationService.Navigate("/UI/Pages/EmployeePage.xaml");
Registering custom views
To register custom views, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var navigationService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var openFileService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Screenshot
Showing
var dependencyResolver = this.GetDependencyResolver();
var pleaseWaitService = dependencyResolver.Resolve<IPleaseWaitService>();
pleaseWaitService.Show();
Hiding
var dependencyResolver = this.GetDependencyResolver();
var pleaseWaitService = dependencyResolver.Resolve<IPleaseWaitService>();
pleaseWaitService.Hide();
Showing and automatically hide
The can automatically hide itself when an action is completed. To use this feature, simply pass a delegate to the metho IPleaseWaitService Show
This is the WPF service screenshot, the exact look differs per framework
d and the service will hide the window as soon as the delegate has completed.
var dependencyResolver = this.GetDependencyResolver();
var pleaseWaitService = dependencyResolver.Resolve<IPleaseWaitService>();
pleaseWaitService.Show(() => Thread.Sleep(1500));
Changing the status
var dependencyResolver = this.GetDependencyResolver();
var pleaseWaitService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var pleaseWaitService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Starting a process with arguments
To start a process with arguments, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var processService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var processService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var saveFileService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var schedulerService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var schedulerService = dependencyResolver.Resolve<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
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.
Silverlight 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var selectDirectoryService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var splashScreenService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.0
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Screenshot
Showing a non-modal window
To show a non-modal window, use the following code:
var viewModel = new EmployeeViewModel();
var dependencyResolver = this.GetDependencyResolver();
var uiVisualizerService = dependencyResolver.Resolve<IUIVisualizerService>();
uiVisualizerService.Show(viewModel);
Showing a modal window
To show a modal window, use the following code:
var viewModel = new EmployeeViewModel();
var dependencyResolver = this.GetDependencyResolver();
var uiVisualizerService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var uiVisualizerService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var uiVisualizerService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
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 dependencyResolver = this.GetDependencyResolver();
var vibrateService = dependencyResolver.Resolve<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 dependencyResolver = this.GetDependencyResolver();
var vibrateService = dependencyResolver.Resolve<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 5
Windows Phone 8.0
Windows Phone 8.1
Windows RT 8.0
Windows RT 8.1
Test/emulation service
Exporting a view
To export a view, use the following code:
var dependencyResolver = this.GetDependencyResolver();
var viewExportService = dependencyResolver.Resolve<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);
_logic.ViewLoading += (sender, e) => ViewLoading.SafeInvoke(this);
_logic.ViewLoaded += (sender, e) => ViewLoaded.SafeInvoke(this);
_logic.ViewUnloading += (sender, e) => ViewUnloading.SafeInvoke(this);
_logic.ViewUnloaded += (sender, e) => ViewUnloaded.SafeInvoke(this);
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;
/// <summary>
/// Occurs when the view model container is loading.
/// </summary>
public event EventHandler<EventArgs> ViewLoading;
/// <summary>
/// Occurs when the view model container is loaded.
/// </summary>
public event EventHandler<EventArgs> ViewLoaded;
/// <summary>
/// Occurs when the view model container starts unloading.
/// </summary>
public event EventHandler<EventArgs> ViewUnloading;
/// <summary>
/// Occurs when the view model container is unloaded.
/// </summary>
public event EventHandler<EventArgs> ViewUnloaded;
}
If you are using .NET / Silverlight, you must add the property below as well:
public bool SkipSearchingForInfoBarMessageControl
{
get { return _logic.GetValue<UserControlLogic, bool>(x =>
x.SkipSearchingForInfoBarMessageControl, true); }
set { _logic.SetValue<UserControlLogic>(x =>
x.SkipSearchingForInfoBarMessageControl = value); }
}
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)
{
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
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);
}
}
_logic = new WindowLogic(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);
_logic.ViewLoading += (sender, e) => ViewLoading.SafeInvoke(this);
_logic.ViewLoaded += (sender, e) => ViewLoaded.SafeInvoke(this);
_logic.ViewUnloading += (sender, e) => ViewUnloading.SafeInvoke(this);
_logic.ViewUnloaded += (sender, e) => ViewUnloaded.SafeInvoke(this);
// 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;
/// <summary>
/// Occurs when the view model container is loading.
/// </summary>
public event EventHandler<EventArgs> ViewLoading;
/// <summary>
/// Occurs when the view model container is loaded.
/// </summary>
public event EventHandler<EventArgs> ViewLoaded;
/// <summary>
/// Occurs when the view model container starts unloading.
/// </summary>
public event EventHandler<EventArgs> ViewUnloading;
/// <summary>
/// Occurs when the view model container is unloaded.
/// </summary>
public event EventHandler<EventArgs> ViewUnloaded;
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