init
This commit is contained in:
492
GLM5Analysis/PromptTemplates/AddNewHardwareSupport.md
Normal file
492
GLM5Analysis/PromptTemplates/AddNewHardwareSupport.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# Add New Hardware Support - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO supports various Data Acquisition System (DAS) hardware through the hardware abstraction layer in `Common/DTS.Common.DAS.Concepts/` and the `DataPRO/Modules/Hardware/` modules. Hardware support involves implementing interfaces for arm/disarm, data collection, real-time streaming, and trigger functionality.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.DAS.Concepts/
|
||||
├── DAS/
|
||||
│ ├── Channel/ # Channel abstraction
|
||||
│ ├── DAS.Id.cs # Hardware identification
|
||||
│ └── DAS.Channel.cs # Channel definitions
|
||||
├── Interfaces/ # Hardware interfaces
|
||||
├── IArmable.cs # Arming capability
|
||||
├── IDataCollectionEnabled.cs # Data collection
|
||||
├── IDownloadEnabled.cs # Data download
|
||||
├── IRealtimeable.cs # Real-time streaming
|
||||
├── ITriggerable.cs # Trigger capability
|
||||
└── IGpioEnabled.cs # GPIO support
|
||||
|
||||
DataPRO/Modules/Hardware/
|
||||
├── HardwareList/ # Hardware management UI
|
||||
│ ├── Model/
|
||||
│ ├── View/
|
||||
│ └── ViewModel/
|
||||
└── AddEditHardware/ # Hardware configuration
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create Hardware Model
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME} :
|
||||
IArmable,
|
||||
IDataCollectionEnabled,
|
||||
IRealtimeable,
|
||||
ITriggerable
|
||||
{
|
||||
public DASId Id { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string FirmwareVersion { get; set; }
|
||||
public int ChannelCount { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
|
||||
public ArmStatus ArmStatus { get; private set; }
|
||||
public bool IsDataCollectionEnabled { get; private set; }
|
||||
public bool IsRealtimeEnabled { get; private set; }
|
||||
|
||||
public {HARDWARE_NAME}()
|
||||
{
|
||||
Id = new DASId();
|
||||
ChannelCount = 8;
|
||||
SampleRate = 100000;
|
||||
ArmStatus = ArmStatus.Disarmed;
|
||||
}
|
||||
|
||||
// IArmable implementation
|
||||
public void Arm()
|
||||
{
|
||||
ValidateArmConditions();
|
||||
SendArmCommand();
|
||||
ArmStatus = ArmStatus.Armed;
|
||||
}
|
||||
|
||||
public void Disarm()
|
||||
{
|
||||
SendDisarmCommand();
|
||||
ArmStatus = ArmStatus.Disarmed;
|
||||
}
|
||||
|
||||
public AvailableArmModes GetAvailableArmModes()
|
||||
{
|
||||
return new AvailableArmModes
|
||||
{
|
||||
Modes = new List<ArmMode>
|
||||
{
|
||||
ArmMode.Triggered,
|
||||
ArmMode.Immediate
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// IDataCollectionEnabled implementation
|
||||
public void EnableDataCollection()
|
||||
{
|
||||
ConfigureDataCollection();
|
||||
IsDataCollectionEnabled = true;
|
||||
}
|
||||
|
||||
public void DisableDataCollection()
|
||||
{
|
||||
StopDataCollection();
|
||||
IsDataCollectionEnabled = false;
|
||||
}
|
||||
|
||||
// IRealtimeable implementation
|
||||
public void StartRealtime()
|
||||
{
|
||||
ValidateRealtimeConditions();
|
||||
StartRealtimeStreaming();
|
||||
IsRealtimeEnabled = true;
|
||||
}
|
||||
|
||||
public void StopRealtime()
|
||||
{
|
||||
StopRealtimeStreaming();
|
||||
IsRealtimeEnabled = false;
|
||||
}
|
||||
|
||||
// ITriggerable implementation
|
||||
public void ConfigureTrigger(TriggerConfiguration config)
|
||||
{
|
||||
ValidateTriggerConfiguration(config);
|
||||
ApplyTriggerSettings(config);
|
||||
}
|
||||
|
||||
// Hardware-specific methods
|
||||
private void ValidateArmConditions()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SerialNumber))
|
||||
throw new InvalidOperationException("Serial number required");
|
||||
}
|
||||
|
||||
private void SendArmCommand()
|
||||
{
|
||||
// Hardware communication implementation
|
||||
}
|
||||
|
||||
private void SendDisarmCommand()
|
||||
{
|
||||
// Hardware communication implementation
|
||||
}
|
||||
|
||||
private void ConfigureDataCollection()
|
||||
{
|
||||
// Configure sample rate, duration, etc.
|
||||
}
|
||||
|
||||
private void StopDataCollection()
|
||||
{
|
||||
// Stop collection
|
||||
}
|
||||
|
||||
private void ValidateRealtimeConditions()
|
||||
{
|
||||
if (!IsDataCollectionEnabled)
|
||||
throw new InvalidOperationException("Enable data collection first");
|
||||
}
|
||||
|
||||
private void StartRealtimeStreaming()
|
||||
{
|
||||
// Start real-time data stream
|
||||
}
|
||||
|
||||
private void StopRealtimeStreaming()
|
||||
{
|
||||
// Stop real-time data stream
|
||||
}
|
||||
|
||||
private void ValidateTriggerConfiguration(TriggerConfiguration config)
|
||||
{
|
||||
if (config.Threshold < 0)
|
||||
throw new ArgumentException("Invalid threshold");
|
||||
}
|
||||
|
||||
private void ApplyTriggerSettings(TriggerConfiguration config)
|
||||
{
|
||||
// Apply trigger configuration to hardware
|
||||
}
|
||||
}
|
||||
|
||||
public class TriggerConfiguration
|
||||
{
|
||||
public double Threshold { get; set; }
|
||||
public TriggerEdge Edge { get; set; }
|
||||
public int Channel { get; set; }
|
||||
}
|
||||
|
||||
public enum TriggerEdge
|
||||
{
|
||||
Rising,
|
||||
Falling,
|
||||
Both
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Hardware Channel Definition
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}Channel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.DAS.Concepts.Channel;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME}Channel : DAS.Channel
|
||||
{
|
||||
public int PhysicalChannel { get; set; }
|
||||
public string Label { get; set; }
|
||||
public double FullScaleRange { get; set; }
|
||||
public double ExcitationVoltage { get; set; }
|
||||
|
||||
public {HARDWARE_NAME}Channel(int index)
|
||||
{
|
||||
PhysicalChannel = index;
|
||||
FullScaleRange = 10.0;
|
||||
ExcitationVoltage = 0.0;
|
||||
}
|
||||
|
||||
public void Configure(ChannelConfiguration config)
|
||||
{
|
||||
FullScaleRange = config.FullScaleRange;
|
||||
ExcitationVoltage = config.ExcitationVoltage;
|
||||
ApplyChannelSettings();
|
||||
}
|
||||
|
||||
private void ApplyChannelSettings()
|
||||
{
|
||||
// Apply channel configuration to hardware
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelConfiguration
|
||||
{
|
||||
public double FullScaleRange { get; set; }
|
||||
public double ExcitationVoltage { get; set; }
|
||||
public CouplingMode Coupling { get; set; }
|
||||
}
|
||||
|
||||
public enum CouplingMode
|
||||
{
|
||||
DC,
|
||||
AC
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create Hardware Factory
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}Factory.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.DAS.Concepts.Interfaces;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME}Factory : IDASFactory
|
||||
{
|
||||
public string HardwareType => "{HARDWARE_NAME}";
|
||||
|
||||
public DAS.Channel CreateChannel(int index)
|
||||
{
|
||||
return new {HARDWARE_NAME}Channel(index);
|
||||
}
|
||||
|
||||
public {HARDWARE_NAME} CreateHardware(string serialNumber)
|
||||
{
|
||||
var hardware = new {HARDWARE_NAME}
|
||||
{
|
||||
SerialNumber = serialNumber
|
||||
};
|
||||
|
||||
InitializeHardware(hardware);
|
||||
return hardware;
|
||||
}
|
||||
|
||||
private void InitializeHardware({HARDWARE_NAME} hardware)
|
||||
{
|
||||
// Hardware initialization
|
||||
// Connect, discover channels, read firmware version
|
||||
}
|
||||
|
||||
public bool CanCreate(string hardwareIdentifier)
|
||||
{
|
||||
// Check if this factory supports the given hardware
|
||||
return hardwareIdentifier.StartsWith("{HARDWARE_PREFIX}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update Hardware List Module
|
||||
**File:** `DataPRO/Modules/Hardware/HardwareList/Model/{HardwareName}Model.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using DTS.Common.DAS.Concepts;
|
||||
|
||||
namespace HardwareList.Model
|
||||
{
|
||||
public class {HARDWARE_NAME}Model : INotifyPropertyChanged
|
||||
{
|
||||
private {HARDWARE_NAME} _hardware;
|
||||
|
||||
public string SerialNumber
|
||||
{
|
||||
get => _hardware?.SerialNumber;
|
||||
set
|
||||
{
|
||||
if (_hardware != null)
|
||||
_hardware.SerialNumber = value;
|
||||
OnPropertyChanged(nameof(SerialNumber));
|
||||
}
|
||||
}
|
||||
|
||||
public string FirmwareVersion => _hardware?.FirmwareVersion;
|
||||
public int ChannelCount => _hardware?.ChannelCount ?? 0;
|
||||
public ArmStatus ArmStatus => _hardware?.ArmStatus ?? ArmStatus.Disarmed;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public void SetHardware({HARDWARE_NAME} hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
OnPropertyChanged(nameof(SerialNumber));
|
||||
OnPropertyChanged(nameof(FirmwareVersion));
|
||||
OnPropertyChanged(nameof(ChannelCount));
|
||||
OnPropertyChanged(nameof(ArmStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Hardware View
|
||||
**File:** `DataPRO/Modules/Hardware/HardwareList/View/{HardwareName}View.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="HardwareList.View.{HARDWARE_NAME}View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:HardwareList.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{x:Static resx:StringResources.{HARDWARE_NAME}_Header}"
|
||||
Style="{StaticResource HardwareHeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{x:Static resx:StringResources.SerialNumber}"/>
|
||||
<TextBox Text="{Binding SerialNumber, Mode=TwoWay}" Width="150" Margin="5,0"/>
|
||||
<TextBlock Text="{x:Static resx:StringResources.Firmware}"/>
|
||||
<TextBlock Text="{Binding FirmwareVersion}" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<TabControl Grid.Row="2">
|
||||
<TabItem Header="{x:Static resx:StringResources.Channels}">
|
||||
<ListView ItemsSource="{Binding Channels}" SelectedItem="{Binding SelectedChannel}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="Channel" DisplayMemberBinding="{Binding PhysicalChannel}"/>
|
||||
<GridViewColumn Header="Label" DisplayMemberBinding="{Binding Label}"/>
|
||||
<GridViewColumn Header="Range" DisplayMemberBinding="{Binding FullScaleRange}"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</TabItem>
|
||||
<TabItem Header="{x:Static resx:StringResources.Settings}">
|
||||
<!-- Hardware-specific settings -->
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 6. Register Factory in Bootstrapper
|
||||
**File:** Modify the bootstrapper or module initialization
|
||||
|
||||
```csharp
|
||||
private void RegisterDASFactories()
|
||||
{
|
||||
// Register existing factories
|
||||
_unityContainer.RegisterType<IDASFactory, SLICE6Factory>("SLICE6");
|
||||
_unityContainer.RegisterType<IDASFactory, TDASFactory>("TDAS");
|
||||
|
||||
// Register new hardware factory
|
||||
_unityContainer.RegisterType<IDASFactory, {HARDWARE_NAME}Factory>("{HARDWARE_NAME}");
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Add Hardware Type to Enums
|
||||
**File:** `Common/DTS.Common/Enums/HardwareTypes.cs`
|
||||
|
||||
```csharp
|
||||
public enum HardwareType
|
||||
{
|
||||
Unknown,
|
||||
SLICE6,
|
||||
TDAS,
|
||||
{HARDWARE_NAME} // Add new type
|
||||
}
|
||||
```
|
||||
|
||||
## Interface Reference
|
||||
|
||||
### IArmable
|
||||
```csharp
|
||||
public interface IArmable
|
||||
{
|
||||
ArmStatus ArmStatus { get; }
|
||||
void Arm();
|
||||
void Disarm();
|
||||
AvailableArmModes GetAvailableArmModes();
|
||||
}
|
||||
```
|
||||
|
||||
### IDataCollectionEnabled
|
||||
```csharp
|
||||
public interface IDataCollectionEnabled
|
||||
{
|
||||
bool IsDataCollectionEnabled { get; }
|
||||
void EnableDataCollection();
|
||||
void DisableDataCollection();
|
||||
}
|
||||
```
|
||||
|
||||
### IRealtimeable
|
||||
```csharp
|
||||
public interface IRealtimeable
|
||||
{
|
||||
bool IsRealtimeEnabled { get; }
|
||||
void StartRealtime();
|
||||
void StopRealtime();
|
||||
}
|
||||
```
|
||||
|
||||
### ITriggerable
|
||||
```csharp
|
||||
public interface ITriggerable
|
||||
{
|
||||
void ConfigureTrigger(TriggerConfiguration config);
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}.cs` | Create |
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}Channel.cs` | Create |
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}Factory.cs` | Create |
|
||||
| `HardwareList/Model/{HardwareName}Model.cs` | Create |
|
||||
| `HardwareList/View/{HardwareName}View.xaml` | Create |
|
||||
| `HardwareList/View/{HardwareName}View.xaml.cs` | Create |
|
||||
| `HardwareList/HardwareListModule.cs` | Modify (register) |
|
||||
| `DTS.Common/Enums/HardwareType.cs` | Modify (add enum) |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Hardware class implements required interfaces (`IArmable`, etc.)
|
||||
- [ ] Channel class inherits from `DAS.Channel`
|
||||
- [ ] Factory implements `IDASFactory`
|
||||
- [ ] All interface methods properly implemented
|
||||
- [ ] Validation added for hardware commands
|
||||
- [ ] Error handling for communication failures
|
||||
- [ ] Factory registered in DI container
|
||||
- [ ] Hardware type added to enum
|
||||
- [ ] View properly data-bound to ViewModel
|
||||
- [ ] Localization strings added
|
||||
- [ ] Channel configuration supports hardware features
|
||||
|
||||
## Hardware Communication Patterns
|
||||
|
||||
1. **Synchronous Commands:** Use for arm/disarm operations
|
||||
2. **Asynchronous Data:** Use for real-time streaming
|
||||
3. **Status Polling:** Implement periodic status checks
|
||||
4. **Error Recovery:** Handle disconnections gracefully
|
||||
5. **Timeout Handling:** Set appropriate timeouts for operations
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
- Always check connection status before sending commands
|
||||
- Validate hardware state transitions (can't arm if already armed)
|
||||
- Handle firmware version differences
|
||||
- Consider backward compatibility with older firmware
|
||||
- Implement proper disposal of resources
|
||||
464
GLM5Analysis/PromptTemplates/AddNewImportFormat.md
Normal file
464
GLM5Analysis/PromptTemplates/AddNewImportFormat.md
Normal file
@@ -0,0 +1,464 @@
|
||||
# Add New Import Format - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO supports importing test data from various file formats through the `Common/DTS.Common.Import/` library. The import system uses a parser-based architecture where each format has a dedicated parser class that converts external data into the internal `ImportObject` structure.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.Import/
|
||||
├── Parsers/
|
||||
│ ├── CSV/ # CSV format parsers
|
||||
│ ├── EQX/ # Equipment Exchange format
|
||||
│ │ ├── EQXSensorsParser.cs
|
||||
│ │ ├── EQXTestSetupParser.cs
|
||||
│ │ └── EQXGroupImport.cs
|
||||
│ ├── DefaultParseImport.cs
|
||||
│ ├── DTSXMLParseImport.cs
|
||||
│ └── ParseVariantBase.cs # Base class for parsers
|
||||
├── ImportObject.cs # Container for imported data
|
||||
├── ImportError.cs # Error handling
|
||||
├── ImportOptions/ # Format-specific options
|
||||
├── Interfaces/ # Parser interfaces
|
||||
├── Persist/ # Database persistence
|
||||
└── XML/ # XML processing utilities
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Parser Class
|
||||
**File:** `Common/DTS.Common.Import/Parsers/{FormatName}/{FormatName}Parser.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DTS.Common.Enums;
|
||||
using DTS.Common.Import.Enums;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using DTS.Common.Import.Parsers;
|
||||
using DTS.Common.Interface.Sensors;
|
||||
using DTS.Common.Storage;
|
||||
using DTS.SensorDB;
|
||||
|
||||
namespace DTS.Common.Import
|
||||
{
|
||||
public class {FORMAT_NAME}Parser : ParseVariantBase
|
||||
{
|
||||
private readonly User _currentUser;
|
||||
private readonly IImportNotification _importNotification;
|
||||
private readonly {FORMAT_NAME}ImportOptions _importOptions;
|
||||
|
||||
public {FORMAT_NAME}Parser(
|
||||
IImportNotification importNotification,
|
||||
User user,
|
||||
{FORMAT_NAME}ImportOptions importOptions)
|
||||
{
|
||||
_currentUser = user;
|
||||
_importNotification = importNotification;
|
||||
_importOptions = importOptions;
|
||||
}
|
||||
|
||||
public override void Parse(ref ImportObject importObject)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (importObject == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(importObject),
|
||||
"importObject can't be null");
|
||||
}
|
||||
|
||||
importObject = ParseFile(importObject, FileName);
|
||||
}
|
||||
|
||||
private ImportObject ParseFile(ImportObject importObject, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate file exists
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
importObject.AddError(new ImportError(
|
||||
$"File not found: {filePath}"));
|
||||
return importObject;
|
||||
}
|
||||
|
||||
// Read and parse file content
|
||||
var content = File.ReadAllText(filePath);
|
||||
var parsedData = ParseContent(content);
|
||||
|
||||
// Populate import object
|
||||
PopulateImportObject(importObject, parsedData);
|
||||
|
||||
// Set source format
|
||||
importObject.SourceFormat = ImportFormats.{FORMAT_NAME};
|
||||
|
||||
_importNotification?.NotifyProgress(100, "Import complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importObject.AddError(new ImportError(
|
||||
$"Parse error: {ex.Message}"));
|
||||
}
|
||||
|
||||
return importObject;
|
||||
}
|
||||
|
||||
private ParsedData ParseContent(string content)
|
||||
{
|
||||
var data = new ParsedData();
|
||||
|
||||
// Format-specific parsing logic here
|
||||
// Example: Parse lines, extract sensors, test setups, etc.
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void PopulateImportObject(ImportObject importObject, ParsedData data)
|
||||
{
|
||||
// Add sensors
|
||||
foreach (var sensor in data.Sensors)
|
||||
{
|
||||
importObject.AddSensor(ConvertToSensorData(sensor));
|
||||
}
|
||||
|
||||
// Add test setups
|
||||
foreach (var setup in data.TestSetups)
|
||||
{
|
||||
importObject.AddTestSetup(ConvertToTestTemplate(setup));
|
||||
}
|
||||
|
||||
// Add hardware
|
||||
foreach (var hardware in data.Hardware)
|
||||
{
|
||||
importObject.AddHardware(ConvertToDASHardware(hardware));
|
||||
}
|
||||
}
|
||||
|
||||
private SensorData ConvertToSensorData(ParsedSensor parsed)
|
||||
{
|
||||
var sensorData = new SensorData
|
||||
{
|
||||
Name = parsed.Name,
|
||||
SerialNumber = parsed.SerialNumber,
|
||||
ChannelCode = parsed.ChannelCode,
|
||||
CalibrationFactor = parsed.CalibrationFactor,
|
||||
EngineeringUnits = parsed.EngineeringUnits,
|
||||
Sensitivity = parsed.Sensitivity
|
||||
};
|
||||
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
private TestTemplate ConvertToTestTemplate(ParsedTestSetup parsed)
|
||||
{
|
||||
// Convert parsed test setup to TestTemplate
|
||||
return new TestTemplate();
|
||||
}
|
||||
|
||||
private DASHardware ConvertToDASHardware(ParsedHardware parsed)
|
||||
{
|
||||
// Convert parsed hardware to DASHardware
|
||||
return new DASHardware();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ParsedData
|
||||
{
|
||||
public List<ParsedSensor> Sensors { get; set; } = new List<ParsedSensor>();
|
||||
public List<ParsedTestSetup> TestSetups { get; set; } = new List<ParsedTestSetup>();
|
||||
public List<ParsedHardware> Hardware { get; set; } = new List<ParsedHardware>();
|
||||
}
|
||||
|
||||
internal class ParsedSensor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string ChannelCode { get; set; }
|
||||
public double CalibrationFactor { get; set; }
|
||||
public string EngineeringUnits { get; set; }
|
||||
public double Sensitivity { get; set; }
|
||||
}
|
||||
|
||||
internal class ParsedTestSetup { }
|
||||
internal class ParsedHardware { }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Import Options Class
|
||||
**File:** `Common/DTS.Common.Import/ImportOptions/{FormatName}ImportOptions.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
namespace DTS.Common.Import.ImportOptions
|
||||
{
|
||||
public class {FORMAT_NAME}ImportOptions
|
||||
{
|
||||
public bool ImportSensors { get; set; } = true;
|
||||
public bool ImportTestSetups { get; set; } = true;
|
||||
public bool ImportHardware { get; set; } = false;
|
||||
public bool CreateGroups { get; set; } = true;
|
||||
public bool ValidateData { get; set; } = true;
|
||||
|
||||
// Format-specific options
|
||||
public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
|
||||
public string Delimiter { get; set; } = ",";
|
||||
public bool HasHeaderRow { get; set; } = true;
|
||||
public int SkipRows { get; set; } = 0;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Delimiter))
|
||||
throw new ArgumentException("Delimiter is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Import Formats Enum
|
||||
**File:** `Common/DTS.Common.Import/Enums/ImportFormats.cs`
|
||||
|
||||
```csharp
|
||||
namespace DTS.Common.Import.Enums
|
||||
{
|
||||
public enum ImportFormats
|
||||
{
|
||||
NOT_SPECIFIED,
|
||||
CSV,
|
||||
EQX,
|
||||
DTS_XML,
|
||||
{FORMAT_NAME} // Add new format
|
||||
}
|
||||
|
||||
public enum ImportFileFormat
|
||||
{
|
||||
NoTestSetup,
|
||||
SingleTestSetup,
|
||||
MultipleTestSetup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Parser Factory Registration
|
||||
**File:** `Common/DTS.Common.Import/Factories/{FormatName}ParserFactory.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using DTS.Common.Import.Parsers;
|
||||
using DTS.Common.Interface.Sensors;
|
||||
using DTS.SensorDB;
|
||||
|
||||
namespace DTS.Common.Import.Factories
|
||||
{
|
||||
public class {FORMAT_NAME}ParserFactory
|
||||
{
|
||||
public static {FORMAT_NAME}Parser Create(
|
||||
IImportNotification importNotification,
|
||||
User user,
|
||||
{FORMAT_NAME}ImportOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
options = new {FORMAT_NAME}ImportOptions();
|
||||
}
|
||||
|
||||
return new {FORMAT_NAME}Parser(importNotification, user, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Add File Detection Logic
|
||||
**File:** `Common/DTS.Common.Import/ImportObject.cs` (modify)
|
||||
|
||||
Add method to detect format from file:
|
||||
```csharp
|
||||
public static ImportFormats DetectFormat(string filePath)
|
||||
{
|
||||
var extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ".csv":
|
||||
return ImportFormats.CSV;
|
||||
case ".eqx":
|
||||
return ImportFormats.EQX;
|
||||
case ".{FORMAT_EXTENSION}":
|
||||
return ImportFormats.{FORMAT_NAME};
|
||||
default:
|
||||
// Check file content for format signature
|
||||
return DetectFormatFromContent(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImportFormats DetectFormatFromContent(string filePath)
|
||||
{
|
||||
// Read first few lines to detect format
|
||||
var firstLine = File.ReadLines(filePath).FirstOrDefault();
|
||||
|
||||
if (firstLine != null)
|
||||
{
|
||||
// Check for format-specific signatures
|
||||
if (firstLine.StartsWith("{FORMAT_SIGNATURE}"))
|
||||
{
|
||||
return ImportFormats.{FORMAT_NAME};
|
||||
}
|
||||
}
|
||||
|
||||
return ImportFormats.NOT_SPECIFIED;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Create Unit Tests
|
||||
**File:** `Common/DTS.Common.Tests/{FormatName}ParserShould.cs`
|
||||
|
||||
```csharp
|
||||
using DTS.Common.Import;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace DTS.Common.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class {FORMAT_NAME}ParserShould
|
||||
{
|
||||
private {FORMAT_NAME}Parser _parser;
|
||||
private {FORMAT_NAME}ImportOptions _options;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_options = new {FORMAT_NAME}ImportOptions();
|
||||
_parser = new {FORMAT_NAME}Parser(null, null, _options);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldReturnEmptyImportObject_WhenFileNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var importObject = new ImportObject();
|
||||
_parser.FileName = null;
|
||||
|
||||
// Act
|
||||
_parser.Parse(ref importObject);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(importObject);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldThrowArgumentNullException_WhenImportObjectIsNull()
|
||||
{
|
||||
// Arrange
|
||||
ImportObject importObject = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => _parser.Parse(ref importObject));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldImportSensors_WhenValidFile()
|
||||
{
|
||||
// Arrange
|
||||
var importObject = new ImportObject();
|
||||
var testFile = CreateTestFile();
|
||||
_parser.FileName = testFile;
|
||||
|
||||
// Act
|
||||
_parser.Parse(ref importObject);
|
||||
|
||||
// Assert
|
||||
// Add assertions for expected data
|
||||
|
||||
// Cleanup
|
||||
File.Delete(testFile);
|
||||
}
|
||||
|
||||
private string CreateTestFile()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
// Write test content
|
||||
File.WriteAllText(path, "test content");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `Parsers/{FormatName}/{FormatName}Parser.cs` | Create |
|
||||
| `ImportOptions/{FormatName}ImportOptions.cs` | Create |
|
||||
| `Factories/{FormatName}ParserFactory.cs` | Create |
|
||||
| `Enums/ImportFormats.cs` | Modify (add enum value) |
|
||||
| `ImportObject.cs` | Modify (add detection logic) |
|
||||
| `DTS.Common.Tests/{FormatName}ParserShould.cs` | Create |
|
||||
|
||||
## Parser Base Class Reference
|
||||
|
||||
```csharp
|
||||
public abstract class ParseVariantBase
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public abstract void Parse(ref ImportObject importObject);
|
||||
}
|
||||
```
|
||||
|
||||
## ImportObject Key Methods
|
||||
|
||||
```csharp
|
||||
// Adding data to import object
|
||||
void AddSensor(SensorData sensor);
|
||||
void AddTestSetup(TestTemplate template);
|
||||
void AddHardware(DASHardware hardware);
|
||||
void AddCalibration(SensorCalibration calibration);
|
||||
|
||||
// Error handling
|
||||
void AddError(ImportError error);
|
||||
IEnumerable<ImportError> Errors();
|
||||
|
||||
// Format detection
|
||||
ImportFormats GetImportFileFormat();
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Parser inherits from `ParseVariantBase`
|
||||
- [ ] `Parse()` method validates null inputs
|
||||
- [ ] File existence checked before parsing
|
||||
- [ ] Errors added to `ImportObject` for failures
|
||||
- [ ] Progress notification via `IImportNotification`
|
||||
- [ ] Source format set on `ImportObject`
|
||||
- [ ] Import options class with validation
|
||||
- [ ] Format added to `ImportFormats` enum
|
||||
- [ ] File detection logic implemented
|
||||
- [ ] Unit tests for happy path and error cases
|
||||
- [ ] Test file cleanup in tests
|
||||
|
||||
## Common Patterns
|
||||
|
||||
1. **Dependency Injection:** Parser receives notification and user objects
|
||||
2. **Error Accumulation:** Add errors to ImportObject rather than throwing
|
||||
3. **Progress Notification:** Call `NotifyProgress()` during long operations
|
||||
4. **File Validation:** Check existence before reading
|
||||
5. **Format Detection:** Check extension and content signature
|
||||
|
||||
## Supported Data Types
|
||||
|
||||
The `ImportObject` can hold:
|
||||
- `SensorData` - Sensor configurations
|
||||
- `TestTemplate` - Test setup definitions
|
||||
- `DASHardware` - Data acquisition hardware
|
||||
- `SensorCalibration` - Calibration data
|
||||
- `Group` - Test object groups
|
||||
- `ISO.CustomerDetails` - Customer information
|
||||
451
GLM5Analysis/PromptTemplates/AddNewReport.md
Normal file
451
GLM5Analysis/PromptTemplates/AddNewReport.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Add New Report - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO reports are implemented as Prism modules in two locations:
|
||||
- `DataPRO/Modules/Reports/` - Full DataPRO reports
|
||||
- `DTS Viewer/DTS.Viewer.Reports/` - Viewer-specific reports
|
||||
|
||||
Reports follow MVVM with separate Input and Output views for parameter collection and results display.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
DataPRO/Modules/Reports/PedestrianAndHeadReports/
|
||||
├── Classes/ # Report generation logic
|
||||
│ ├── ReportBase.cs # Base class for reports
|
||||
│ ├── ExportBase.cs # Export functionality
|
||||
│ └── {ReportName}Export.cs # Specific export logic
|
||||
├── View/
|
||||
│ ├── {ReportName}InputView.xaml # Parameter input UI
|
||||
│ └── {ReportName}OutputView.xaml # Results display UI
|
||||
├── ViewModel/
|
||||
│ └── {ReportName}ViewModel.cs
|
||||
├── Resources/ # Localization
|
||||
└── {ReportName}Module.cs # Module registration
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Report Module Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/{ReportName}Module.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Media.Imaging;
|
||||
using DTS.Common;
|
||||
using DTS.Common.Interface;
|
||||
using Microsoft.Practices.Prism.Modularity;
|
||||
using Microsoft.Practices.Unity;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
[Export(typeof(IModule))]
|
||||
[Module(ModuleName = "{REPORT_NAME}Module")]
|
||||
public class {REPORT_NAME}Module : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public {REPORT_NAME}Module(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}InputView, {REPORT_NAME}InputView>();
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}OutputView, {REPORT_NAME}OutputView>();
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}ViewModel, {REPORT_NAME}ViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||
public class {REPORT_NAME}ImageAttribute : ImageAttribute
|
||||
{
|
||||
private BitmapImage _img;
|
||||
|
||||
public {REPORT_NAME}ImageAttribute() : this(null) { }
|
||||
|
||||
public override BitmapImage AssemblyImage
|
||||
{
|
||||
get { _img = AssemblyInfo.GetImage(AssemblyNames.DB.ToString()); return _img; }
|
||||
}
|
||||
|
||||
public {REPORT_NAME}ImageAttribute(string s)
|
||||
{
|
||||
_img = AssemblyInfo.GetImage(AssemblyNames.DB.ToString());
|
||||
}
|
||||
|
||||
public override Type GetAttributeType() => typeof(ImageAttribute);
|
||||
public override BitmapImage GetAssemblyImage() => AssemblyImage;
|
||||
|
||||
private string _name;
|
||||
public override string AssemblyName
|
||||
{
|
||||
get { _name = AssemblyNames.{REPORT_GROUP}.ToString(); return _name; }
|
||||
}
|
||||
|
||||
public override string GetAssemblyName() => AssemblyName;
|
||||
|
||||
private string _group;
|
||||
public override string AssemblyGroup
|
||||
{
|
||||
get { _group = eAssemblyGroups.Administrative.ToString(); return _group; }
|
||||
}
|
||||
|
||||
public override string GetAssemblyGroup() => AssemblyGroup;
|
||||
|
||||
public override eAssemblyRegion GetAssemblyRegion()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override eAssemblyRegion AssemblyRegion => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create the Report Base Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Classes/{ReportName}.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DTS.Common.Storage;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}Report
|
||||
{
|
||||
public string ReportTitle { get; set; }
|
||||
public DateTime GeneratedDate { get; set; }
|
||||
public List<ReportChannel> Channels { get; set; }
|
||||
|
||||
public {REPORT_NAME}Report()
|
||||
{
|
||||
Channels = new List<ReportChannel>();
|
||||
GeneratedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public void Generate(TestSetup testSetup, ReportParameters parameters)
|
||||
{
|
||||
// Validate inputs
|
||||
if (testSetup == null)
|
||||
throw new ArgumentNullException(nameof(testSetup));
|
||||
|
||||
// Generate report data
|
||||
ProcessData(testSetup, parameters);
|
||||
}
|
||||
|
||||
private void ProcessData(TestSetup testSetup, ReportParameters parameters)
|
||||
{
|
||||
// Implementation specific to report type
|
||||
}
|
||||
}
|
||||
|
||||
public class ReportChannel
|
||||
{
|
||||
public string ChannelName { get; set; }
|
||||
public double PeakValue { get; set; }
|
||||
public double Duration { get; set; }
|
||||
}
|
||||
|
||||
public class ReportParameters
|
||||
{
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
public double Threshold { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create the Export Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Classes/{ReportName}Export.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using DTS.Common.Storage;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}Export : ExportBase
|
||||
{
|
||||
public {REPORT_NAME}Report Report { get; set; }
|
||||
|
||||
public void ExportToCSV(string filePath)
|
||||
{
|
||||
if (Report == null)
|
||||
throw new InvalidOperationException("Report not generated");
|
||||
|
||||
using (var writer = new StreamWriter(filePath))
|
||||
{
|
||||
WriteHeader(writer);
|
||||
WriteData(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportToExcel(string filePath)
|
||||
{
|
||||
// Excel export implementation
|
||||
}
|
||||
|
||||
private void WriteHeader(StreamWriter writer)
|
||||
{
|
||||
writer.WriteLine($"Report,{Report.ReportTitle}");
|
||||
writer.WriteLine($"Generated,{Report.GeneratedDate:yyyy-MM-dd HH:mm:ss}");
|
||||
writer.WriteLine();
|
||||
writer.WriteLine("Channel,Peak Value,Duration");
|
||||
}
|
||||
|
||||
private void WriteData(StreamWriter writer)
|
||||
{
|
||||
foreach (var channel in Report.Channels)
|
||||
{
|
||||
writer.WriteLine($"{channel.ChannelName},{channel.PeakValue},{channel.Duration}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Input View (XAML)
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/View/{ReportName}InputView.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="{REPORT_NAME}.{REPORT_NAME}InputView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:{REPORT_NAME}.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{x:Static resx:StringResources.ReportParameters}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical" Margin="5">
|
||||
<DatePicker SelectedDate="{Binding StartDate, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.StartDate}"/>
|
||||
<DatePicker SelectedDate="{Binding EndDate, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.EndDate}"/>
|
||||
<TextBox Text="{Binding Threshold, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.Threshold}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="2" Content="{x:Static resx:StringResources.Generate}"
|
||||
Command="{Binding GenerateCommand}"
|
||||
HorizontalAlignment="Right" Margin="5"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 5. Create Output View (XAML)
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/View/{ReportName}OutputView.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="{REPORT_NAME}.{REPORT_NAME}OutputView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:{REPORT_NAME}.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{Binding ReportTitle}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<DataGrid Grid.Row="1" ItemsSource="{Binding ReportData}"
|
||||
AutoGenerateColumns="False" IsReadOnly="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Channel" Binding="{Binding ChannelName}"/>
|
||||
<DataGridTextColumn Header="Peak Value" Binding="{Binding PeakValue}"/>
|
||||
<DataGridTextColumn Header="Duration" Binding="{Binding Duration}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="{x:Static resx:StringResources.ExportCSV}"
|
||||
Command="{Binding ExportCSVCommand}" Margin="5"/>
|
||||
<Button Content="{x:Static resx:StringResources.ExportExcel}"
|
||||
Command="{Binding ExportExcelCommand}" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 6. Create the ViewModel
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/ViewModel/{ReportName}ViewModel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
using DTS.Common.Interface;
|
||||
using Microsoft.Practices.Prism.Commands;
|
||||
using Microsoft.Practices.Unity;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}ViewModel : I{REPORT_NAME}ViewModel, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{REPORT_NAME}InputView InputView { get; set; }
|
||||
public I{REPORT_NAME}OutputView OutputView { get; set; }
|
||||
|
||||
private DateTime _startDate;
|
||||
public DateTime StartDate
|
||||
{
|
||||
get => _startDate;
|
||||
set { _startDate = value; OnPropertyChanged(nameof(StartDate)); }
|
||||
}
|
||||
|
||||
private DateTime _endDate;
|
||||
public DateTime EndDate
|
||||
{
|
||||
get => _endDate;
|
||||
set { _endDate = value; OnPropertyChanged(nameof(EndDate)); }
|
||||
}
|
||||
|
||||
private double _threshold;
|
||||
public double Threshold
|
||||
{
|
||||
get => _threshold;
|
||||
set { _threshold = value; OnPropertyChanged(nameof(Threshold)); }
|
||||
}
|
||||
|
||||
public ObservableCollection<ReportChannel> ReportData { get; set; }
|
||||
|
||||
public ICommand GenerateCommand { get; }
|
||||
public ICommand ExportCSVCommand { get; }
|
||||
public ICommand ExportExcelCommand { get; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {REPORT_NAME}ViewModel(
|
||||
I{REPORT_NAME}InputView inputView,
|
||||
I{REPORT_NAME}OutputView outputView,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
InputView = inputView;
|
||||
InputView.DataContext = this;
|
||||
OutputView = outputView;
|
||||
OutputView.DataContext = this;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
ReportData = new ObservableCollection<ReportChannel>();
|
||||
|
||||
GenerateCommand = new DelegateCommand(OnGenerate);
|
||||
ExportCSVCommand = new DelegateCommand(OnExportCSV);
|
||||
ExportExcelCommand = new DelegateCommand(OnExportExcel);
|
||||
|
||||
InitializeDefaults();
|
||||
}
|
||||
|
||||
private void InitializeDefaults()
|
||||
{
|
||||
StartDate = DateTime.Now.AddDays(-7);
|
||||
EndDate = DateTime.Now;
|
||||
Threshold = 0.0;
|
||||
}
|
||||
|
||||
private void OnGenerate()
|
||||
{
|
||||
// Generate report logic
|
||||
var report = new {REPORT_NAME}Report();
|
||||
// ... generate data
|
||||
}
|
||||
|
||||
private void OnExportCSV()
|
||||
{
|
||||
// Export to CSV
|
||||
}
|
||||
|
||||
private void OnExportExcel()
|
||||
{
|
||||
// Export to Excel
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Add Localization Resources
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Resources/StringResources.resx`
|
||||
|
||||
Add required strings:
|
||||
- `ReportParameters`
|
||||
- `StartDate`
|
||||
- `EndDate`
|
||||
- `Threshold`
|
||||
- `Generate`
|
||||
- `ExportCSV`
|
||||
- `ExportExcel`
|
||||
|
||||
## For DTS Viewer Reports
|
||||
If creating a Viewer report, use this location:
|
||||
```
|
||||
DTS Viewer/DTS.Viewer.Reports/DTS.Viewer.{ReportName}/
|
||||
```
|
||||
|
||||
The module class differs slightly:
|
||||
```csharp
|
||||
[Module(ModuleName = "{REPORT_NAME}")]
|
||||
public class {REPORT_NAME}Module : I{REPORT_NAME}Module
|
||||
{
|
||||
public bool SessionStarted { get; private set; }
|
||||
|
||||
public void StartSession()
|
||||
{
|
||||
var eventAggregator = _unityContainer.Resolve<IEventAggregator>();
|
||||
eventAggregator.GetEvent<LoadViewModulEvent>().Publish(new LoadViewModulArg());
|
||||
SessionStarted = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `{ReportName}/{ReportName}Module.cs` | Create |
|
||||
| `{ReportName}/Classes/{ReportName}.cs` | Create |
|
||||
| `{ReportName}/Classes/{ReportName}Export.cs` | Create |
|
||||
| `{ReportName}/View/{ReportName}InputView.xaml` | Create |
|
||||
| `{ReportName}/View/{ReportName}InputView.xaml.cs` | Create |
|
||||
| `{ReportName}/View/{ReportName}OutputView.xaml` | Create |
|
||||
| `{ReportName}/View/{ReportName}OutputView.xaml.cs` | Create |
|
||||
| `{ReportName}/ViewModel/{ReportName}ViewModel.cs` | Create |
|
||||
| `{ReportName}/Resources/StringResources.resx` | Create |
|
||||
| `DTS.Common/Interface/{ReportName}Interfaces.cs` | Create |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Module registered with `[Module]` attribute
|
||||
- [ ] Assembly image attribute defined
|
||||
- [ ] Input/Output views follow naming convention
|
||||
- [ ] ViewModel implements `INotifyPropertyChanged`
|
||||
- [ ] Commands use `DelegateCommand` from Prism
|
||||
- [ ] Export methods handle file I/O properly
|
||||
- [ ] Localization strings for all UI text
|
||||
- [ ] Report generation validates inputs
|
||||
- [ ] Error handling implemented
|
||||
- [ ] Assembly group set appropriately (`eAssemblyGroups.Administrative`)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
1. **Two-View Pattern:** Reports use separate Input and Output views
|
||||
2. **ExportBase Inheritance:** Export classes inherit from `ExportBase`
|
||||
3. **ObservableCollection:** Use for data binding in ViewModels
|
||||
4. **DelegateCommand:** Use Prism's `DelegateCommand` for ICommand implementation
|
||||
244
GLM5Analysis/PromptTemplates/AddNewSensorType.md
Normal file
244
GLM5Analysis/PromptTemplates/AddNewSensorType.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Add New Sensor Type - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO manages sensor configurations through the `DataPRO/Modules/SensorsList/` module. The system supports various sensor types (Analog, Digital I/O, Squib, UART, Stream) with a consistent MVVM architecture using Prism modularity and Unity dependency injection.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
DataPRO/Modules/SensorsList/
|
||||
├── SensorsList/ # Main sensor list management
|
||||
│ ├── Model/ # Sensor data models
|
||||
│ ├── View/ # XAML views
|
||||
│ ├── ViewModel/ # Business logic
|
||||
│ ├── Resources/ # Localization
|
||||
│ └── SensorsListModule.cs # Module registration
|
||||
├── SensorSettingsModule/ # Sensor configuration UI
|
||||
└── SoftwareFilters/ # Signal processing filters
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Sensor Model
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/Model/{SensorName}Setting.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DTS.Common.Classes.Sensors;
|
||||
using DTS.Common.Enums.Sensors;
|
||||
|
||||
namespace SensorsList.Model
|
||||
{
|
||||
public class {SENSOR_NAME}Setting : ISensorSetting
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string ChannelCode { get; set; }
|
||||
|
||||
// Add sensor-specific properties
|
||||
public double CalibrationFactor { get; set; }
|
||||
public string EngineeringUnits { get; set; }
|
||||
|
||||
// Required interface members
|
||||
public KnownChannelTypes ChannelType => KnownChannelTypes.{CHANNEL_TYPE};
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
throw new ArgumentException("Name is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create the View (XAML)
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/View/{SensorName}View.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="SensorsList.View.{SENSOR_NAME}View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:SensorsList.Resources"
|
||||
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="{x:Static resx:StringResources.{SENSOR_NAME}_Header}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||
<TextBox Text="{Binding {SENSOR_NAME}Name, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.Name}"/>
|
||||
<!-- Add sensor-specific controls -->
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
**Code-behind:** `DataPRO/Modules/SensorsList/SensorsList/View/{SensorName}View.xaml.cs`
|
||||
|
||||
```csharp
|
||||
using System.Windows.Controls;
|
||||
using DTS.Common.Interface.Sensors.SensorsList;
|
||||
|
||||
namespace SensorsList.View
|
||||
{
|
||||
public partial class {SENSOR_NAME}View : UserControl, I{SENSOR_NAME}View
|
||||
{
|
||||
public {SENSOR_NAME}View()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create the ViewModel
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/ViewModel/{SensorName}ViewModel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using DTS.Common.Interface.Sensors.SensorsList;
|
||||
using DTS.Common.Events.Sensors.SensorsList;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
|
||||
namespace SensorsList
|
||||
{
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {SENSOR_NAME}ViewModel : I{SENSOR_NAME}ViewModel, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{SENSOR_NAME}View View { get; set; }
|
||||
|
||||
private ObservableCollection<{SENSOR_NAME}Setting> _items;
|
||||
public ObservableCollection<{SENSOR_NAME}Setting> Items
|
||||
{
|
||||
get => _items;
|
||||
set { _items = value; OnPropertyChanged(nameof(Items)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {SENSOR_NAME}ViewModel(
|
||||
I{SENSOR_NAME}View view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
_regionManager = regionManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
Items = new ObservableCollection<{SENSOR_NAME}Setting>();
|
||||
// Subscribe to events
|
||||
_eventAggregator.GetEvent<{SENSOR_NAME}UpdatedEvent>().Subscribe(On{SENSOR_NAME}Updated);
|
||||
}
|
||||
|
||||
private void On{SENSOR_NAME}Updated({SENSOR_NAME}Setting setting)
|
||||
{
|
||||
// Handle update logic
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Interface
|
||||
**File:** `Common/DTS.Common/Interface/Sensors/SensorsList/I{SensorName}View.cs`
|
||||
|
||||
```csharp
|
||||
namespace DTS.Common.Interface.Sensors.SensorsList
|
||||
{
|
||||
public interface I{SENSOR_NAME}View
|
||||
{
|
||||
object DataContext { get; set; }
|
||||
}
|
||||
|
||||
public interface I{SENSOR_NAME}ViewModel
|
||||
{
|
||||
I{SENSOR_NAME}View View { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Update Module Registration
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/SensorsListModule.cs`
|
||||
|
||||
Add to the `Initialize()` method:
|
||||
```csharp
|
||||
_unityContainer.RegisterType<I{SENSOR_NAME}View, {SENSOR_NAME}View>();
|
||||
_unityContainer.RegisterType<I{SENSOR_NAME}ViewModel, {SENSOR_NAME}ViewModel>();
|
||||
```
|
||||
|
||||
### 6. Add Localization Strings
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/Resources/StringResources.resx`
|
||||
|
||||
Add entries:
|
||||
- `{SENSOR_NAME}_Header` - Display header
|
||||
- `{SENSOR_NAME}_Description` - Description text
|
||||
|
||||
### 7. Add Channel Type Enum (if new)
|
||||
**File:** `Common/DTS.Common/Enums/Sensors/KnownChannelTypes.cs`
|
||||
|
||||
```csharp
|
||||
public enum KnownChannelTypes
|
||||
{
|
||||
// Existing types...
|
||||
{SENSOR_TYPE_CODE} // Add new type
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `SensorsList/Model/{SensorName}Setting.cs` | Create |
|
||||
| `SensorsList/View/{SensorName}View.xaml` | Create |
|
||||
| `SensorsList/View/{SensorName}View.xaml.cs` | Create |
|
||||
| `SensorsList/ViewModel/{SensorName}ViewModel.cs` | Create |
|
||||
| `DTS.Common/Interface/Sensors/SensorsList/I{SensorName}View.cs` | Create |
|
||||
| `SensorsList/SensorsListModule.cs` | Modify (register types) |
|
||||
| `SensorsList/Resources/StringResources.resx` | Modify (add strings) |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Model implements `ISensorSetting` interface
|
||||
- [ ] View implements corresponding interface
|
||||
- [ ] ViewModel has `[PartCreationPolicy(CreationPolicy.Shared)]` attribute
|
||||
- [ ] Types registered in module's `Initialize()` method
|
||||
- [ ] Localization strings added for all UI text
|
||||
- [ ] Property change notifications implemented
|
||||
- [ ] Event subscriptions properly managed
|
||||
- [ ] Constructor injection follows existing patterns
|
||||
- [ ] XAML uses resource references for strings (`{x:Static resx:StringResources...}`)
|
||||
- [ ] Channel type added to enum if new sensor category
|
||||
|
||||
## Common Patterns to Follow
|
||||
|
||||
1. **Naming Convention:** Use PascalCase for class names, camelCase for private fields with underscore prefix
|
||||
2. **Dependency Injection:** All dependencies injected via constructor
|
||||
3. **Events:** Use `IEventAggregator` for cross-module communication
|
||||
4. **Regions:** Register views with appropriate region (`eAssemblyRegion.SensorsListRegion`)
|
||||
5. **ReSharper Annotations:** Include `// ReSharper disable` comments at file top as needed
|
||||
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Add Unit Test - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO uses NUnit for unit testing with test projects in `Common/DTS.Common.Tests/`. Tests follow the Arrange-Act-Assert (AAA) pattern and use `TestCaseSource` for parameterized tests. The testing framework emphasizes clear test naming and comprehensive coverage of edge cases.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.Tests/
|
||||
├── ChannelTypeUtilityShould.cs # Example test file
|
||||
├── FilterClassShould.cs
|
||||
├── GroupChannelShould.cs
|
||||
├── LinearizationFormulaShould.cs
|
||||
├── NetworkUtilsShould.cs
|
||||
├── DTS.Common.Tests.csproj
|
||||
└── Properties/
|
||||
└── AssemblyInfo.cs
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create Test Class
|
||||
**File:** `Common/DTS.Common.Tests/{ClassName}Should.cs`
|
||||
|
||||
```csharp
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DTS.Common.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class {CLASS_NAME}Should
|
||||
{
|
||||
private {CLASS_NAME} _sut; // System Under Test
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_sut = new {CLASS_NAME}();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Cleanup if needed
|
||||
_sut = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MethodName_ShouldReturnExpectedResult_WhenGivenValidInput()
|
||||
{
|
||||
// Arrange
|
||||
var input = "valid input";
|
||||
var expected = "expected output";
|
||||
|
||||
// Act
|
||||
var result = _sut.MethodName(input);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Basic Test Patterns
|
||||
|
||||
#### Simple Assertion Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void CalculateTotal_ShouldReturnSum_WhenGivenValidNumbers()
|
||||
{
|
||||
// Arrange
|
||||
var calculator = new Calculator();
|
||||
var numbers = new List<int> { 1, 2, 3, 4, 5 };
|
||||
var expected = 15;
|
||||
|
||||
// Act
|
||||
var result = calculator.CalculateTotal(numbers);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Null/Empty Input Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedNull()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput(null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedEmptyString()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput("");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
```
|
||||
|
||||
#### Exception Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void Divide_ShouldThrowDivideByZeroException_WhenDivisorIsZero()
|
||||
{
|
||||
// Arrange
|
||||
var dividend = 10;
|
||||
var divisor = 0;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<DivideByZeroException>(() => _sut.Divide(dividend, divisor));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetName_ShouldThrowArgumentException_WhenNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
string name = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => _sut.SetName(name));
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Parameterized Tests
|
||||
|
||||
#### Using TestCase
|
||||
```csharp
|
||||
[TestCase(1, 2, 3)]
|
||||
[TestCase(10, 20, 30)]
|
||||
[TestCase(-5, 5, 0)]
|
||||
[TestCase(0, 0, 0)]
|
||||
public void Add_ShouldReturnCorrectSum_WhenGivenTwoNumbers(int a, int b, int expected)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.Add(a, b);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Using TestCaseSource
|
||||
```csharp
|
||||
public static IEnumerable<TestCaseData> ChannelTypeTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("AC087155.1", "AC");
|
||||
yield return new TestCaseData("DC123456.2", "DC");
|
||||
yield return new TestCaseData("TM987654.3", "TM");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ChannelTypeTestCases))]
|
||||
public void ParseChannelType_ShouldReturnCorrectType_WhenGivenValidName(
|
||||
string sensorName, string expectedType)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedType));
|
||||
}
|
||||
```
|
||||
|
||||
#### Generating Test Data from Enum
|
||||
```csharp
|
||||
public static Array GetKnownChannelTypes()
|
||||
{
|
||||
var testValuesFromEnum = new List<string>();
|
||||
var values = Enum.GetValues(typeof(KnownChannelTypes))
|
||||
.Cast<KnownChannelTypes>()
|
||||
.Select(x => x.ToString())
|
||||
.ToArray();
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
testValuesFromEnum.Add($"{value}087155.1");
|
||||
}
|
||||
|
||||
return testValuesFromEnum.ToArray();
|
||||
}
|
||||
|
||||
[TestCaseSource("GetKnownChannelTypes")]
|
||||
public void ParseSensorKnownChannelType_ShouldReturnCorrectTag_WhenPassedSensorNameWithCorrectPrefix(
|
||||
string sensorName)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = ChannelTypeUtility.ParseSensorKnownChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(Enum.IsDefined(typeof(KnownChannelTypes), result));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Testing Async Methods
|
||||
```csharp
|
||||
[Test]
|
||||
public async Task LoadDataAsync_ShouldReturnData_WhenDataExists()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCount = 5;
|
||||
|
||||
// Act
|
||||
var result = await _sut.LoadDataAsync();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result.Count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProcessAsync_ShouldThrowException_WhenInputInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var invalidInput = "";
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await _sut.ProcessAsync(invalidInput));
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Testing Events
|
||||
```csharp
|
||||
[Test]
|
||||
public void ValueChanged_ShouldRaiseEvent_WhenValueIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var eventRaised = false;
|
||||
_sut.ValueChanged += (sender, args) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
_sut.Value = 42;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(eventRaised, "ValueChanged event was not raised");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PropertyChanged_ShouldBeRaised_WhenNameChanges()
|
||||
{
|
||||
// Arrange
|
||||
var eventArgs = new List<string>();
|
||||
_sut.PropertyChanged += (sender, e) => eventArgs.Add(e.PropertyName);
|
||||
|
||||
// Act
|
||||
_sut.Name = "New Name";
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Name", eventArgs);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Testing Collections
|
||||
```csharp
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnEmptyCollection_WhenNoItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddItem_ShouldIncreaseCount_WhenItemIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var item = new Item { Id = 1, Name = "Test" };
|
||||
var initialCount = _sut.ItemCount;
|
||||
|
||||
// Act
|
||||
_sut.AddItem(item);
|
||||
|
||||
// Assert
|
||||
Assert.That(_sut.ItemCount, Is.EqualTo(initialCount + 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnItemsInOrder_WhenItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
_sut.AddItem(new Item { Id = 3 });
|
||||
_sut.AddItem(new Item { Id = 1 });
|
||||
_sut.AddItem(new Item { Id = 2 });
|
||||
|
||||
// Act
|
||||
var result = _sut.GetItems().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(result[0].Id, Is.EqualTo(1));
|
||||
Assert.That(result[1].Id, Is.EqualTo(2));
|
||||
Assert.That(result[2].Id, Is.EqualTo(3));
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Mocking Dependencies (if needed)
|
||||
```csharp
|
||||
using Moq;
|
||||
|
||||
[TestFixture]
|
||||
public class SensorServiceShould
|
||||
{
|
||||
private Mock<ISensorRepository> _mockRepository;
|
||||
private SensorService _sut;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockRepository = new Mock<ISensorRepository>();
|
||||
_sut = new SensorService(_mockRepository.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSensor_ShouldReturnSensor_WhenSensorExists()
|
||||
{
|
||||
// Arrange
|
||||
var sensorId = 123;
|
||||
var expectedSensor = new Sensor { Id = sensorId, Name = "Test" };
|
||||
_mockRepository.Setup(r => r.Find(sensorId)).Returns(expectedSensor);
|
||||
|
||||
// Act
|
||||
var result = _sut.GetSensor(sensorId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedSensor));
|
||||
_mockRepository.Verify(r => r.Find(sensorId), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveSensor_ShouldCallRepository_WhenSensorIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var sensor = new Sensor { Id = 1, Name = "Test" };
|
||||
|
||||
// Act
|
||||
_sut.SaveSensor(sensor);
|
||||
|
||||
// Assert
|
||||
_mockRepository.Verify(r => r.Save(sensor), Times.Once);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Naming Convention
|
||||
|
||||
Follow this pattern: `MethodName_Scenario_ExpectedResult`
|
||||
|
||||
Examples:
|
||||
- `ParseSensorName_ShouldReturnNull_WhenPassedNull`
|
||||
- `ParseSensorName_ShouldReturnEmpty_WhenPassedEmptyString`
|
||||
- `ParseSensorName_ShouldReturnCorrectTag_WhenPassedValidName`
|
||||
- `CalculateTotal_ShouldThrowException_WhenListIsNull`
|
||||
|
||||
## Common Assertions
|
||||
|
||||
```csharp
|
||||
// Equality
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
Assert.That(result, Is.Not.EqualTo(wrongValue));
|
||||
|
||||
// Null checks
|
||||
Assert.That(result, Is.Null);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
|
||||
// Boolean
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(result, Is.False);
|
||||
|
||||
// Collections
|
||||
Assert.That(collection, Is.Empty);
|
||||
Assert.That(collection, Is.Not.Empty);
|
||||
Assert.That(collection, Has.Count.EqualTo(5));
|
||||
Assert.That(collection, Does.Contain(item));
|
||||
|
||||
// Exceptions
|
||||
Assert.Throws<ArgumentException>(() => method());
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await method());
|
||||
|
||||
// String
|
||||
Assert.That(result, Does.StartWith("prefix"));
|
||||
Assert.That(result, Does.EndWith("suffix"));
|
||||
Assert.That(result, Does.Contain("substring"));
|
||||
Assert.That(result, Is.Empty);
|
||||
|
||||
// Range
|
||||
Assert.That(value, Is.InRange(1, 10));
|
||||
Assert.That(value, Is.GreaterThan(0));
|
||||
Assert.That(value, Is.LessThan(100));
|
||||
|
||||
// Type checking
|
||||
Assert.That(result, Is.TypeOf<ExpectedType>());
|
||||
Assert.That(result, Is.InstanceOf<IBaseInterface>());
|
||||
```
|
||||
|
||||
## Files to Create Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `DTS.Common.Tests/{ClassName}Should.cs` | Create |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Test class has `[TestFixture]` attribute
|
||||
- [ ] Test methods have `[Test]` attribute
|
||||
- [ ] `[SetUp]` used for test initialization
|
||||
- [ ] `[TearDown]` used for cleanup (if needed)
|
||||
- [ ] Test names follow naming convention
|
||||
- [ ] Each test has Arrange-Act-Assert sections
|
||||
- [ ] Edge cases tested (null, empty, boundary values)
|
||||
- [ ] Exception cases tested
|
||||
- [ ] Test is isolated (doesn't depend on other tests)
|
||||
- [ ] No external dependencies (use mocks/stubs)
|
||||
- [ ] Assertions are specific and meaningful
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests in project
|
||||
dotnet test Common/DTS.Common.Tests/
|
||||
|
||||
# Run specific test class
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould"
|
||||
|
||||
# Run specific test method
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould.ParseSensorKnownChannelType_ShouldReturnEmpty_WhenPassedNull"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **One assertion per test** (or logically related assertions)
|
||||
2. **Test behavior, not implementation**
|
||||
3. **Use meaningful test names that describe the scenario**
|
||||
4. **Keep tests independent** - no shared state between tests
|
||||
5. **Test edge cases** - null, empty, max values, boundary conditions
|
||||
6. **Don't test private methods directly** - test through public API
|
||||
7. **Use parameterized tests for similar test cases with different data**
|
||||
8. **Mock external dependencies** - database, file system, network
|
||||
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