# MVVM Pattern ## When to Use - All UI development in DataPRO - Creating views that need to be testable - Separating business logic from presentation ## Files to Create/Modify ### Standard Structure ``` {Module}/ ├── Interface/ │ ├── I{ViewModelName}.cs (in DTS.Common or DTS.CommonCore) │ └── I{ViewName}.cs (in DTS.Common or DTS.CommonCore) ├── View/ │ ├── {ViewName}.xaml │ └── {ViewName}.xaml.cs └── ViewModel/ └── {ViewModelName}.cs ``` ## Code Templates ### 1. View Interface **File:** `DTS.CommonCore/Interface/{Module}/I{ViewName}.cs` ```csharp using DTS.Common.Base; namespace DTS.Common.Interface.{Module} { public interface I{ViewName} : IBaseView { // Add view-specific properties/methods if needed } } ``` ### 2. ViewModel Interface **File:** `DTS.CommonCore/Interface/{Module}/I{ViewModelName}.cs` ```csharp using DTS.Common.Base; namespace DTS.Common.Interface.{Module} { public interface I{ViewModelName} : IBaseViewModel { I{ViewName} View { get; set; } // Add other properties/methods } } ``` ### 3. ViewModel Implementation ```csharp using System; using System.ComponentModel; using System.ComponentModel.Composition; using Prism.Events; using Prism.Regions; using Unity; using DTS.Common.Interface.{Module}; using DTS.Common.Interactivity; using DTS.Common.Events; namespace {ModuleName} { [PartCreationPolicy(CreationPolicy.Shared)] public class {ViewModelName} : I{ViewModelName} { public I{ViewName} View { get; set; } private IEventAggregator _eventAggregator { get; } private readonly IRegionManager _regionManager; private IUnityContainer UnityContainer { get; } public InteractionRequest NotificationRequest { get; } public InteractionRequest ConfirmationRequest { get; } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public {ViewModelName}( I{ViewName} view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) { View = view; View.DataContext = this; NotificationRequest = new InteractionRequest(); ConfirmationRequest = new InteractionRequest(); _eventAggregator = eventAggregator; UnityContainer = unityContainer; _regionManager = regionManager; _eventAggregator.GetEvent().Subscribe(OnRaiseNotification); _eventAggregator.GetEvent() .Subscribe(OnBusyIndicatorNotification, ThreadOption.PublisherThread, true); } private bool _isBusy = false; public bool IsBusy { get => _isBusy; set { _isBusy = value; OnPropertyChanged("IsBusy"); } } public bool IsDirty { get; private set; } public bool IsMenuIncluded { get; set; } public bool IsNavigationIncluded { get; set; } public void Initialize() { } public void Initialize(object parameter) { } public Task InitializeAsync() => Task.CompletedTask; public Task InitializeAsync(object parameter) => Task.CompletedTask; public void Activated() { } public void Cleanup() { } public Task CleanupAsync() => Task.CompletedTask; private void OnBusyIndicatorNotification(bool eventArg) => IsBusy = eventArg; private void OnRaiseNotification(NotificationContentEventArgs eventArgs) { NotificationRequest.Raise(new Notification { Content = eventArgs, Title = eventArgs.Title }); } } } ``` ### 4. XAML View ```xml ``` ### 5. View Code-Behind ```csharp using System.Windows.Controls; using DTS.Common.Base; using DTS.Common.Interface.{Module}; namespace {ModuleName} { public partial class {ViewName} : BaseView, I{ViewName} { public {ViewName}() { InitializeComponent(); } } } ``` ## Examples from Codebase ### Example 1: SensorsListViewModel **File:** `DataPRO/Modules/SensorsList/SensorsList/ViewModel/SensorsListViewModel.cs:41` ```csharp [PartCreationPolicy(CreationPolicy.Shared)] public class SensorsListViewModel : ISensorsListViewModel { public ISensorsListView View { get; set; } private IEventAggregator _eventAggregator { get; } private readonly IRegionManager _regionManager; private IUnityContainer UnityContainer { get; } public SensorsListViewModel( ISensorsListView view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) { View = view; View.DataContext = this; _eventAggregator = eventAggregator; // ... } } ``` ### Example 2: ShellViewModel (BindableBase) **File:** `DataPRO/DataPRO/ViewModel/ShellViewModel.cs:26` ```csharp [Export(typeof(IShellView))] [PartCreationPolicy(CreationPolicy.Shared)] public class ShellViewModel : BindableBase, IShellViewModel { public IShellView View { get; private set; } public ShellViewModel( IShellView view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) { View = view; View.DataContext = this; // ... } } ``` ### Example 3: ISensorsListViewModel Interface **File:** `Common/DTS.CommonCore/Interface/Sensors/SensorsList/ISensorsListViewModel.cs:8` ```csharp public interface ISensorsListViewModel : IBaseViewModel, IFilterableListView { ISensorsListView View { get; set; } IAnalogSensor[] AnalogSensors { get; set; } void GetSensors(int sensorCalWarningPeriodDays, bool included); void Filter(string currentFilter); } ``` ### Example 4: XAML Binding Pattern **File:** `DataPRO/Modules/SensorsList/SensorsList/View/SensorsListView.xaml:44` ```xml ``` ## Common Mistakes to Avoid 1. **Not setting DataContext in constructor** - View won't bind to ViewModel 2. **Forgetting INotifyPropertyChanged** - UI won't update when properties change 3. **Using concrete types instead of interfaces** - Breaks DI and testability 4. **Putting logic in code-behind** - Should be in ViewModel 5. **Not using InteractionRequest for dialogs** - Use `NotificationRequest.Raise()` instead of `MessageBox.Show()` 6. **Hardcoded strings in OnPropertyChanged** - Use `nameof()` when possible 7. **Not implementing IBaseViewModel fully** - Missing Initialize/Cleanup methods 8. **Blocking UI thread** - Use async/await for long operations