init
This commit is contained in:
452
GLM5Analysis/PromptTemplates/FixBugInViewModel.md
Normal file
452
GLM5Analysis/PromptTemplates/FixBugInViewModel.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# Fix Bug in ViewModel - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO uses the MVVM (Model-View-ViewModel) pattern with Prism framework and Unity dependency injection. ViewModels contain business logic and state management, binding to Views through XAML data binding. Common issues include property change notification problems, command binding failures, and event subscription memory leaks.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
ViewModel Pattern:
|
||||
┌─────────────┐ Data Binding ┌─────────────┐
|
||||
│ View │◄────────────────────►│ ViewModel │
|
||||
│ (XAML) │ │ (.cs) │
|
||||
└─────────────┘ └─────────────┘
|
||||
│ │
|
||||
│ Code-behind │
|
||||
▼ ▼
|
||||
┌─────────────┐ Services/Events ┌─────────────┐
|
||||
│ IView │◄───────────────────────►│ Services │
|
||||
│ Interface │ │ (DI) │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
## Common ViewModel Issues and Solutions
|
||||
|
||||
### Issue 1: Property Change Not Reflected in UI
|
||||
|
||||
**Symptoms:**
|
||||
- Property value changes but UI doesn't update
|
||||
- No visual feedback on data changes
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Missing PropertyChanged notification
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value; // Missing notification!
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 2: Command Not Executing
|
||||
|
||||
**Symptoms:**
|
||||
- Button click doesn't trigger action
|
||||
- Command's CanExecute always returns false
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Command not properly initialized or CanExecute not raising
|
||||
public ICommand SaveCommand { get; set; }
|
||||
|
||||
// In constructor - missing initialization
|
||||
SaveCommand = new DelegateCommand(OnSave); // Missing CanExecute check
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
// Using Prism's DelegateCommand
|
||||
public ICommand SaveCommand { get; }
|
||||
|
||||
// In constructor
|
||||
SaveCommand = new DelegateCommand(OnSave, CanSave);
|
||||
|
||||
private void OnSave()
|
||||
{
|
||||
// Save logic
|
||||
}
|
||||
|
||||
private bool CanSave()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Name) && HasChanges;
|
||||
}
|
||||
|
||||
// Call when conditions change
|
||||
private void RaiseCanExecuteChanged()
|
||||
{
|
||||
(SaveCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
// Call after property changes that affect CanExecute
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
RaiseCanExecuteChanged(); // Update command state
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 3: Event Subscription Memory Leak
|
||||
|
||||
**Symptoms:**
|
||||
- ViewModel not garbage collected
|
||||
- Multiple event handlers executing
|
||||
- Memory usage increasing
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Not unsubscribing from events
|
||||
public MyViewModel(IEventAggregator eventAggregator)
|
||||
{
|
||||
eventAggregator.GetEvent<DataChangedEvent>().Subscribe(OnDataChanged);
|
||||
// Missing: Keep subscriber reference for unsubscribe
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using Prism.Events;
|
||||
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private SubscriptionToken _dataChangedToken;
|
||||
|
||||
public MyViewModel(IEventAggregator eventAggregator)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
|
||||
// Keep subscription token
|
||||
_dataChangedToken = _eventAggregator.GetEvent<DataChangedEvent>()
|
||||
.Subscribe(OnDataChanged, ThreadOption.PublisherThread, false,
|
||||
data => data != null);
|
||||
}
|
||||
|
||||
// Implement IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dataChangedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<DataChangedEvent>().Unsubscribe(_dataChangedToken);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 4: Thread Affinity Problems
|
||||
|
||||
**Symptoms:**
|
||||
- "The calling thread cannot access this object" exception
|
||||
- UI freezing during operations
|
||||
- Data updated on wrong thread
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Updating UI-bound property from background thread
|
||||
Task.Run(() => {
|
||||
Status = "Processing..."; // Cross-thread violation
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using System.Windows.Threading;
|
||||
|
||||
// Option 1: Use Dispatcher
|
||||
Task.Run(() => {
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Status = "Processing...";
|
||||
});
|
||||
});
|
||||
|
||||
// Option 2: Use ThreadOption in event subscription
|
||||
_eventAggregator.GetEvent<DataChangedEvent>()
|
||||
.Subscribe(OnDataChanged, ThreadOption.UIThread);
|
||||
|
||||
// Option 3: Use async/await properly
|
||||
public async Task LoadDataAsync()
|
||||
{
|
||||
Status = "Loading..."; // On UI thread
|
||||
var data = await _service.GetDataAsync(); // Background
|
||||
Items = new ObservableCollection<DataItem>(data); // Back on UI thread
|
||||
Status = "Complete";
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 5: Collection Changes Not Notifying
|
||||
|
||||
**Symptoms:**
|
||||
- Items added to collection don't appear in UI
|
||||
- ListView/DataGrid not updating
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Using List instead of ObservableCollection
|
||||
public List<SensorItem> Sensors { get; set; }
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
// Use ObservableCollection for UI binding
|
||||
public ObservableCollection<SensorItem> Sensors { get; }
|
||||
|
||||
public MyViewModel()
|
||||
{
|
||||
Sensors = new ObservableCollection<SensorItem>();
|
||||
}
|
||||
|
||||
// To add items from background thread:
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Sensors.Add(newItem);
|
||||
});
|
||||
|
||||
// For bulk updates, clear and add range:
|
||||
public void UpdateSensors(List<SensorItem> newItems)
|
||||
{
|
||||
Sensors.Clear();
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
Sensors.Add(item);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Approach
|
||||
|
||||
### Step 1: Check Property Implementation
|
||||
```csharp
|
||||
// Add debug output to property setter
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Name: {_name} -> {value}");
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
System.Diagnostics.Debug.WriteLine($"OnPropertyChanged raised for Name");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Verify Binding in View
|
||||
```xml
|
||||
<!-- Add PresentationTraceSources for binding debug -->
|
||||
<TextBox Text="{Binding Name, Mode=TwoWay,
|
||||
diagnostics:PresentationTraceSources.TraceLevel=High}"
|
||||
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"/>
|
||||
```
|
||||
|
||||
### Step 3: Check DataContext
|
||||
```csharp
|
||||
// In View code-behind, verify DataContext
|
||||
public partial class MyView : UserControl
|
||||
{
|
||||
public MyView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContextChanged += (s, e) =>
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"DataContext: {e.NewValue?.GetType().Name}");
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Verify Event Subscriptions
|
||||
```csharp
|
||||
// Add debug output to event handlers
|
||||
private void OnDataChanged(DataChangedEventArgs args)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"OnDataChanged called: {args?.Data}");
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Check Command Binding
|
||||
```csharp
|
||||
// Add debug output to command methods
|
||||
private void OnExecute()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("OnExecute called");
|
||||
}
|
||||
|
||||
private bool CanExecute()
|
||||
{
|
||||
var result = !string.IsNullOrEmpty(Name);
|
||||
System.Diagnostics.Debug.WriteLine($"CanExecute: {result}");
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## ViewModel Template Reference
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
using DTS.Common.Interface;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
|
||||
namespace {NAMESPACE}
|
||||
{
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {VIEWMODEL_NAME} : I{VIEWMODEL_NAME}, INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{VIEW_NAME} View { get; set; }
|
||||
|
||||
#region Properties with Change Notification
|
||||
|
||||
private string _status;
|
||||
public string Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
if (_status != value)
|
||||
{
|
||||
_status = value;
|
||||
OnPropertyChanged(nameof(Status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collections
|
||||
|
||||
public ObservableCollection<Item> Items { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {VIEWMODEL_NAME}(
|
||||
I{VIEW_NAME} view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
_regionManager = regionManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
// Initialize commands
|
||||
SaveCommand = new DelegateCommand(OnSave, CanSave);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
|
||||
// Initialize collections
|
||||
Items = new ObservableCollection<Item>();
|
||||
|
||||
// Subscribe to events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
// Event subscriptions
|
||||
}
|
||||
|
||||
private void OnSave()
|
||||
{
|
||||
// Save implementation
|
||||
}
|
||||
|
||||
private bool CanSave()
|
||||
{
|
||||
return true; // Add validation logic
|
||||
}
|
||||
|
||||
private void OnCancel()
|
||||
{
|
||||
// Cancel implementation
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Unsubscribe from events
|
||||
// Dispose resources
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After fixing a ViewModel bug:
|
||||
|
||||
- [ ] PropertyChanged event raised for all bound properties
|
||||
- [ ] Commands use DelegateCommand with proper CanExecute
|
||||
- [ ] RaiseCanExecuteChanged called when command state changes
|
||||
- [ ] Event subscriptions use subscription tokens
|
||||
- [ ] Unsubscribe from events in Dispose
|
||||
- [ ] ObservableCollection used for collections
|
||||
- [ ] Dispatcher used for cross-thread updates
|
||||
- [ ] Async/await used for long-running operations
|
||||
- [ ] DataContext set correctly in constructor
|
||||
- [ ] View implements corresponding interface
|
||||
|
||||
## Quick Reference: Common Patterns
|
||||
|
||||
| Pattern | Implementation |
|
||||
|---------|---------------|
|
||||
| Property Notification | `OnPropertyChanged(nameof(PropertyName))` |
|
||||
| Command | `new DelegateCommand(Execute, CanExecute)` |
|
||||
| Event Subscription | `_eventAggregator.GetEvent<T>().Subscribe(Handler)` |
|
||||
| Thread-Safe Update | `Dispatcher.Invoke(() => Property = value)` |
|
||||
| Collection | `ObservableCollection<T>` |
|
||||
| Validation | `RaiseCanExecuteChanged()` in property setters |
|
||||
Reference in New Issue
Block a user