8.7 KiB
8.7 KiB
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
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
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
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
<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
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
[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
[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
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
<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
- Not setting DataContext in constructor - View won't bind to ViewModel
- Forgetting INotifyPropertyChanged - UI won't update when properties change
- Using concrete types instead of interfaces - Breaks DI and testability
- Putting logic in code-behind - Should be in ViewModel
- Not using InteractionRequest for dialogs - Use
NotificationRequest.Raise()instead ofMessageBox.Show() - Hardcoded strings in OnPropertyChanged - Use
nameof()when possible - Not implementing IBaseViewModel fully - Missing Initialize/Cleanup methods
- Blocking UI thread - Use async/await for long operations