init
This commit is contained in:
275
GLM5Analysis/PatternLibrary/MVVM_Pattern.md
Normal file
275
GLM5Analysis/PatternLibrary/MVVM_Pattern.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 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<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> 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<Notification>();
|
||||
ConfirmationRequest = new InteractionRequest<Confirmation>();
|
||||
|
||||
_eventAggregator = eventAggregator;
|
||||
UnityContainer = unityContainer;
|
||||
_regionManager = regionManager;
|
||||
|
||||
_eventAggregator.GetEvent<RaiseNotification>().Subscribe(OnRaiseNotification);
|
||||
_eventAggregator.GetEvent<BusyIndicatorChangeNotification>()
|
||||
.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
|
||||
<base:BaseView x:Class="{ModuleName}.{ViewName}"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:DTS.Common.Base;assembly=DTS.Common"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:converters="clr-namespace:DTS.Common.Converters;assembly=DTS.Common">
|
||||
|
||||
<base:BaseView.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/DTS.Common;component/Themes/CommonStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibility" />
|
||||
</ResourceDictionary>
|
||||
</base:BaseView.Resources>
|
||||
|
||||
<Grid>
|
||||
<!-- Controls bound to ViewModel properties -->
|
||||
<ListView ItemsSource="{Binding Items}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem" BasedOn="{StaticResource TTS_ListViewItemStyle}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Status}" Value="Error">
|
||||
<Setter Property="Background" Value="{StaticResource Brush_ApplicationStatus_Failed}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</base:BaseView>
|
||||
```
|
||||
|
||||
### 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
|
||||
<ListView ItemsSource="{Binding AnalogSensors, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedIndex="{Binding SelectedAnalogIndex, Mode=TwoWay}">
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:MultiSelectionBehavior SelectedItems="{Binding SelectedAnalogItems}" />
|
||||
</i:Interaction.Behaviors>
|
||||
</ListView>
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user