diff --git a/.build/BuildToolkit.ps1 b/.build/BuildToolkit.ps1 index 20fcab0..afa7fcc 100644 --- a/.build/BuildToolkit.ps1 +++ b/.build/BuildToolkit.ps1 @@ -1,8 +1,9 @@ # Tool Versions $NunitVersion = "3.12.0"; -$OpenCoverVersion = "4.7.922"; -$DocFxVersion = "2.56.2"; -$ReportGeneratorVersion = "4.8.7"; +$OpenCoverVersion = "4.7.1221"; +$DocFxVersion = "2.58.4"; +$ReportGeneratorVersion = "4.8.13"; +$OpenCoverToCoberturaVersion = "0.3.4"; # Folder Pathes $RootPath = $MyInvocation.PSScriptRoot; @@ -18,6 +19,7 @@ $DocumentationArtifcacts = "$ArtifactsDir\Documentation"; # Tests $NunitReportsDir = "$ArtifactsDir\Tests"; $OpenCoverReportsDir = "$ArtifactsDir\Tests" +$CoberturaReportsDir = "$ArtifactsDir\Tests" # Nuget $NugetConfig = "$RootPath\NuGet.Config"; @@ -34,6 +36,7 @@ $global:OpenCoverCli = "$BuildTools\OpenCover.$OpenCoverVersion\tools\OpenCover. $global:NunitCli = "$BuildTools\NUnit.ConsoleRunner.$NunitVersion\tools\nunit3-console.exe"; $global:ReportGeneratorCli = "$BuildTools\ReportGenerator.$ReportGeneratorVersion\tools\net47\ReportGenerator.exe"; $global:DocFxCli = "$BuildTools\docfx.console.$DocFxVersion\tools\docfx.exe"; +$global:OpenCoverToCoberturaCli = "$BuildTools\OpenCoverToCoberturaConverter.$OpenCoverToCoberturaVersion\tools\OpenCoverToCoberturaConverter.exe"; # Git $global:GitCommitHash = ""; @@ -132,6 +135,7 @@ function Invoke-Initialize([string]$Version = "1.0.0", [bool]$Cleanup = $False) Write-Variable "NUnitCli" $global:NUnitCli; Write-Variable "ReportGeneratorCli" $global:ReportGeneratorCli; Write-Variable "DocFxCli" $global:DocFxCli; + Write-Variable "OpenCoverToCoberturaCli" $global:OpenCoverToCoberturaCli; Write-Variable "GitCli" $global:GitCli; Write-Variable "GitCommitHash" $global:GitCommitHash; @@ -269,6 +273,10 @@ function Invoke-CoverTests($SearchPath = $RootPath, $SearchFilter = "*.csproj", Install-Tool "OpenCover" $OpenCoverVersion $global:OpenCoverCli; } + if (-not (Test-Path $global:OpenCoverToCoberturaCli)) { + Install-Tool "OpenCoverToCoberturaConverter" $OpenCoverToCoberturaVersion $global:OpenCoverToCoberturaCli; + } + CreateFolderIfNotExists $OpenCoverReportsDir; CreateFolderIfNotExists $NunitReportsDir; @@ -308,6 +316,7 @@ function Invoke-CoverTests($SearchPath = $RootPath, $SearchFilter = "*.csproj", $nunitXml = ($NunitReportsDir + "\$projectName.TestResult.xml"); $openCoverXml = ($OpenCoverReportsDir + "\$projectName.OpenCover.xml"); + $coberturaXml = ($CoberturaReportsDir + "\$projectName.Cobertura.xml"); if ($isNetCore) { $targetArgs = '"test -v ' + $env:MORYX_TEST_VERBOSITY + ' -c ' + $env:MORYX_BUILD_CONFIG + ' ' + $testProject + '"'; @@ -346,6 +355,9 @@ function Invoke-CoverTests($SearchPath = $RootPath, $SearchFilter = "*.csproj", Write-Host-Error "Nunit exited with $errorText for $projectName"; Invoke-ExitCodeCheck $exitCode; } + + & $global:OpenCoverToCoberturaCli -input:$openCoverXml -output:$coberturaXml -sources:$rootPath + Invoke-ExitCodeCheck $LastExitCode; } } @@ -655,7 +667,7 @@ function CreateFolderIfNotExists([string]$Folder) { } function CopyAndReplaceFolder($SourceDir, $TargetDir) { - Write-Host-Info "Copy $TargetDir to $SourceDir!" + Write-Host-Info "Copy $SourceDir to $TargetDir!" # Remove old folder if exists if (Test-Path $TargetDir) { Write-Host "Target path already exists, removing ..." -ForegroundColor Yellow @@ -665,4 +677,4 @@ function CopyAndReplaceFolder($SourceDir, $TargetDir) { # Copy to target path Write-Host "Copy from $SourceDir to $TargetDir ..." -ForegroundColor Green Copy-Item -Path $SourceDir -Recurse -Destination $TargetDir -Container -} \ No newline at end of file +} diff --git a/.build/Global.DotSettings b/.build/Global.DotSettings deleted file mode 100644 index f1329ac..0000000 --- a/.build/Global.DotSettings +++ /dev/null @@ -1,12 +0,0 @@ - - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - DO_NOT_SHOW - LINE_BREAK - True - True - True - True - 2 - \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7e75a14..86789d6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -3,19 +3,21 @@ name: CI # Controls when the action will run. Triggers the workflow on push or pull request on: push: - branches: + branches: - dev + - future tags: - v[0-9]+.[0-9]+.[0-9]+ # Matches all semantic versioning tags with major, minor, patch pull_request: - branches: + branches: - dev + - future env: MORYX_OPTIMIZE_CODE: "false" MORYX_BUILD_CONFIG: "Release" MORYX_BUILDNUMBER: ${{github.run_number}} - dotnet_sdk_version: '5.0.100' + dotnet_sdk_version: '5.0.403' DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true jobs: @@ -112,8 +114,8 @@ jobs: name: packages path: artifacts/Packages/ - - name: Publish on MyGet - if: ${{ startsWith(github.ref, 'refs/heads/') }} # Normal branches are published to myget + - name: Publish on MyGet-CI + if: ${{ github.ref == 'refs/heads/dev' }} # dev branche is published to myget moryx shell: pwsh env: MORYX_NUGET_APIKEY: ${{secrets.MYGET_TOKEN}} @@ -121,6 +123,15 @@ jobs: MORYX_PACKAGE_TARGET_V3: "https://www.myget.org/F/moryx/api/v3/index.json" run: ./Build.ps1 -Publish + - name: Publish on MyGet-Future + if: ${{ github.ref == 'refs/heads/future' }} # Future branch is published to myget moryx-future + shell: pwsh + env: + MORYX_NUGET_APIKEY: ${{secrets.MYGET_TOKEN}} + MORYX_PACKAGE_TARGET: "https://www.myget.org/F/moryx-future/api/v2/package" + MORYX_PACKAGE_TARGET_V3: "https://www.myget.org/F/moryx-future/api/v3/index.json" + run: ./Build.ps1 -Publish + - name: Publish on NuGet if: ${{ startsWith(github.ref, 'refs/tags/v') }} # Version Tags are published to nuget shell: pwsh diff --git a/Directory.Build.targets b/Directory.Build.targets index 0d17468..ce8065d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -18,10 +18,10 @@ - - - - + + + + diff --git a/README.md b/README.md index 73c913a..f19684d 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,13 @@ The **MORYX ClientFramework** is the foundation for MORYX WPF desktop frontends. If you want to start developing with or for MORYX, the easiest way is our [template repository](https://github.com/PHOENIXCONTACT/MORYX-Template). It comes with two empty solutions, the necessary package feeds and preinstalled empty MORYX runtime. Add projects and packages to backend and frontend solutions depending on your specific requirements. Install stable releases via Nuget; development releases are available via MyGet. -| Package Name | Release (NuGet) | CI (MyGet) | -|--------------|-----------------|------------| -| `Moryx.WpfToolkit` | [![NuGet](https://img.shields.io/nuget/v/Moryx.WpfToolkit.svg)](https://www.nuget.org/packages/Moryx.WpfToolkit/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.WpfToolkit)](https://www.myget.org/feed/moryx/package/nuget/Moryx.WpfToolkit) | -| `Moryx.Controls` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Controls.svg)](https://www.nuget.org/packages/Moryx.Controls/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Controls)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Controls) | -| `Moryx.ClientFramework` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.svg)](https://www.nuget.org/packages/Moryx.ClientFramework/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework) | -| `Moryx.ClientFramework.Configurator` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.Configurator.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.Configurator/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.Configurator)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.Configurator) | -| `Moryx.ClientFramework.Kernel` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.Kernel.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.Kernel/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.Kernel)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.Kernel) | -| `Moryx.ClientFramework.SimpleShell` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.SimpleShell.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.SimpleShell/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.SimpleShell)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.SimpleShell) | -| `Moryx.Tools.WcfClient.UI.Viewer` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Tools.WcfClient.UI.Viewer.svg)](https://www.nuget.org/packages/Moryx.Tools.WcfClient.UI.Viewer/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Tools.WcfClient.UI.Viewer)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Tools.WcfClient.UI.Viewer) | +| Package Name | Release (NuGet) | CI (MyGet) | Future (MyGet) | +|--------------|-----------------|------------|----------------| +| `Moryx.WpfToolkit` | [![NuGet](https://img.shields.io/nuget/v/Moryx.WpfToolkit.svg)](https://www.nuget.org/packages/Moryx.WpfToolkit/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.WpfToolkit)](https://www.myget.org/feed/moryx/package/nuget/Moryx.WpfToolkit) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.WpfToolkit)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.WpfToolkit) | +| `Moryx.Controls` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Controls.svg)](https://www.nuget.org/packages/Moryx.Controls/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Controls)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Controls) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.Controls)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.Controls) | +| `Moryx.ClientFramework` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.svg)](https://www.nuget.org/packages/Moryx.ClientFramework/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ClientFramework)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ClientFramework) | +| `Moryx.ClientFramework.Kernel` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.Kernel.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.Kernel/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.Kernel)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.Kernel) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ClientFramework.Kernel)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ClientFramework.Kernel) | +| `Moryx.ClientFramework.Configurator` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.Configurator.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.Configurator/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.Configurator)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.Configurator) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ClientFramework.Configurator)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ClientFramework.Configurator) | +| `Moryx.ClientFramework.SimpleShell` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.SimpleShell.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.SimpleShell/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.SimpleShell)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.SimpleShell) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ClientFramework.SimpleShell)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ClientFramework.SimpleShell) | +| `Moryx.ClientFramework.Wcf` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ClientFramework.Wcf.svg)](https://www.nuget.org/packages/Moryx.ClientFramework.Wcf/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ClientFramework.Wcf)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ClientFramework.Wcf) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ClientFramework.Wcf)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ClientFramework.Wcf) | +| `Moryx.Tools.WcfClient.UI.Viewer` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Tools.WcfClient.UI.Viewer.svg)](https://www.nuget.org/packages/Moryx.Tools.WcfClient.UI.Viewer/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Tools.WcfClient.UI.Viewer)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Tools.WcfClient.UI.Viewer) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.Tools.WcfClient.UI.Viewer)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.Tools.WcfClient.UI.Viewer) | diff --git a/VERSION b/VERSION index a0cd9f0..944880f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.0 \ No newline at end of file +3.2.0 diff --git a/src/Moryx.Controls.Demo/ViewModels/EntryEditorViewModel.cs b/src/Moryx.Controls.Demo/ViewModels/EntryEditorViewModel.cs index 5fc06e5..feffc22 100644 --- a/src/Moryx.Controls.Demo/ViewModels/EntryEditorViewModel.cs +++ b/src/Moryx.Controls.Demo/ViewModels/EntryEditorViewModel.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020, Phoenix Contact GmbH & Co. KG // Licensed under the Apache License, Version 2.0 +using System; using System.Collections.Generic; using System.Windows; using System.Windows.Input; @@ -13,12 +14,30 @@ namespace Moryx.Controls.Demo.ViewModels { public class EntryEditorViewModel : Screen { + private bool _isEditMode; + public override string DisplayName => "EntryEditor"; public EntryViewModel EntryViewModels { get; } public ICommand ShowExceptionCommand { get; } + public ICommand BeginEditCmd { get; } + + public ICommand EndEditCmd { get; } + + public ICommand CancelEditCmd { get; } + + public bool IsEditMode + { + get { return _isEditMode; } + private set + { + _isEditMode = value; + NotifyOfPropertyChange(); + } + } + public EntryEditorViewModel() { var entryModel = new EntryClass @@ -62,6 +81,26 @@ public EntryEditorViewModel() EntryViewModels = new EntryViewModel(entry); ShowExceptionCommand = new RelayCommand(ShowException); + BeginEditCmd = new RelayCommand(BeginEdit); + EndEditCmd = new RelayCommand(EndEdit); + CancelEditCmd = new RelayCommand(CancelEdit); + } + + private void EndEdit(object obj) + { + throw new NotImplementedException(); + } + + private void CancelEdit(object obj) + { + IsEditMode = false; + EntryViewModels.CancelEdit(); + } + + private void BeginEdit(object obj) + { + IsEditMode = true; + EntryViewModels.BeginEdit(); } public void ShowException(object parameter) diff --git a/src/Moryx.Controls.Demo/Views/EntryEditorView.xaml b/src/Moryx.Controls.Demo/Views/EntryEditorView.xaml index 43f0683..559fc24 100644 --- a/src/Moryx.Controls.Demo/Views/EntryEditorView.xaml +++ b/src/Moryx.Controls.Demo/Views/EntryEditorView.xaml @@ -11,5 +11,22 @@ xmlns:controls="clr-namespace:Moryx.Controls;assembly=Moryx.Controls" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:EntryEditorViewModel}"> - + + + + + + + + + diff --git a/src/Moryx.Controls/EntryEditor/EntryViewModel.cs b/src/Moryx.Controls/EntryEditor/EntryViewModel.cs index 41b7435..e4abf95 100644 --- a/src/Moryx.Controls/EntryEditor/EntryViewModel.cs +++ b/src/Moryx.Controls/EntryEditor/EntryViewModel.cs @@ -1,40 +1,125 @@ // Copyright (c) 2020, Phoenix Contact GmbH & Co. KG // Licensed under the Apache License, Version 2.0 +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using Moryx.Serialization; +using Moryx.Tools; namespace Moryx.Controls { /// /// View model that represents a page in the config editor /// - public class EntryViewModel : INotifyPropertyChanged + public class EntryViewModel : INotifyPropertyChanged, IEditableObject { + + #region Fields and Properties + private ObservableCollection _subEntries; + private string _preEditValue; + /// /// The entry of this view model /// - public Entry Entry { get; } + public Entry Entry { get; private set; } /// /// Parent view model /// public EntryViewModel Parent { get; set; } + /// + /// Displayed name of the + /// + public string DisplayName { get; private set; } + + /// + /// Type of the value of the + /// + public EntryValueType ValueType { get; private set; } + + /// + /// Type of the unit of the value of the + /// + public EntryUnitType UnitType { get; private set; } + + /// + /// Default value of the + /// + public string DefaultValue => Entry.Value.Default; + + /// + /// Description of the + /// + public string Description => Entry.Description; + + /// + /// Flag if this entry is readonly and text boxes shall be disabled + /// + public bool IsReadOnly => Entry.Value.IsReadOnly; + + // TODO: AL6 Remove direct access to Model and the helper variable _preEditValue from BeginEdit, CancelEdit and EndEdit methods + /// + /// Current value of + /// + public string Value + { + get => Entry.Value.Current; + set + { + if (Entry.Value.Current is not null && Entry.Value.Current.Equals(value)) + return; + Entry.Value.Current = value; + OnPropertyChanged(); + } + } + + /// + /// Assignable values to the current + /// + public ObservableCollection PossibleValues + { + get + { + var possibleValues = Entry?.Value.Possible; + return possibleValues != null ? new ObservableCollection(possibleValues) : null; + } + } + + /// + /// Child instances of this + /// + public ObservableCollection SubEntries + { + get => _subEntries; + set + { + _subEntries = value; + OnPropertyChanged(); + } + } + #endregion + + #region Constructors /// /// Initializes a new instance of the class. /// - public EntryViewModel(IList entries) + public EntryViewModel() : this(new Entry() { DisplayName = "Root", + Value = new EntryValue() { Type = EntryValueType.Class }, SubEntries = new List() }) { - DisplayName = "Root"; - ValueType = EntryValueType.Class; - SubEntries = new ObservableWrapperCollection(entries); + } - UpdateParent(); + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use Empty constructor instead")] + public EntryViewModel(IList entries) : this() + { + UpdateModel(entries); } /// @@ -44,57 +129,27 @@ public EntryViewModel(Entry entry) { Entry = entry; DisplayName = Entry.DisplayName; + Value = Entry.Value.Current; ValueType = Entry.Value.Type; UnitType = Entry.Value.UnitType; - SubEntries = new ObservableWrapperCollection(entry.SubEntries); + SubEntries = new ObservableCollection(Entry.SubEntries.Select(e => new EntryViewModel(e))); UpdateParent(); } - - private void UpdateParent(EntryViewModel entry) - { - entry.Parent = this; - } + #endregion private void UpdateParent() { foreach (var entryViewModel in SubEntries) - { UpdateParent(entryViewModel); - } } - /// - public string DisplayName { get; } - - /// - public string Value + private void UpdateParent(EntryViewModel entry) { - get => Entry.Value.Current; - set - { - Entry.Value.Current = value; - OnPropertyChanged(); - } + if (entry.Parent is null || !entry.Parent.Equals(this)) + entry.Parent = this; } - /// - public EntryValueType ValueType { get; } - - /// - public EntryUnitType UnitType { get; } - - /// - public string DefaultValue => Entry.Value.Default; - - /// - public string Description => Entry.Description; - - /// - /// Flag if this entry is readonly and text boxes shall be disabled - /// - public bool IsReadOnly => Entry.Value.IsReadOnly; - /// /// Add prototype of this name to the subEntries /// @@ -117,68 +172,103 @@ public void ReplaceWithPrototype(string prototypeName) // Update entry Entry.Value = prototype.Value; Entry.SubEntries = prototype.SubEntries; + Entry.Value.Type = prototype.Value.Type; + Entry.Value.UnitType = prototype.Value.UnitType; // Update our observable collection - SubEntries = new ObservableWrapperCollection(Entry.SubEntries); + SubEntries = new ObservableCollection(Entry.SubEntries.Select(e => new EntryViewModel(e))); UpdateParent(); } - /// - public ObservableCollection PossibleValues + /// + public void BeginEdit() { - get - { - var possibleValues = Entry?.Value.Possible; - return possibleValues != null ? new ObservableCollection(possibleValues) : null; - } + SubEntries.BeginEdit(); + _preEditValue = Entry.Value.Current; } - private ObservableCollection _subEntries; + /// + public void EndEdit() + { + SubEntries.EndEdit(); + _preEditValue = Entry.Value.Current; + CopyToModel(); + } - /// - public ObservableCollection SubEntries + private void CopyToModel() { - get => _subEntries; - set - { - _subEntries = value; - OnPropertyChanged(); - } + Entry.DisplayName = DisplayName; + Entry.Value.Current = Value; + Entry.Value.Type = ValueType; + Entry.Value.UnitType = UnitType; + Entry.SubEntries = SubEntries.Select(vm => vm.Entry).ToList(); + + UpdateParent(); } /// - public event PropertyChangedEventHandler PropertyChanged; - - private void OnPropertyChanged([CallerMemberName] string propertyName = null) + public void CancelEdit() { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + SubEntries.CancelEdit(); + Value = _preEditValue; + CopyFromModel(); } - } - internal class ObservableWrapperCollection : ObservableCollection - { - private readonly IList _entries; + private void CopyFromModel() + { + DisplayName = Entry.DisplayName; + UnitType = Entry.Value.UnitType; + ValueType = Entry.Value.Type; + MergeIntoViewModelCollection(SubEntries, Entry.SubEntries); - public ObservableWrapperCollection(IList entries) + UpdateParent(); + } + + private void MergeIntoViewModelCollection(ObservableCollection entryViewModels, IList updated) { - _entries = entries; - // Copy current state to our collection - for (var i = 0; i < entries.Count; i++) + // Remove those without identifiert or not existing in the updated collection + var removed = entryViewModels.Where(vm => vm.Entry.Identifier == "" || updated.All(e => e.Identifier != vm.Entry.Identifier)).ToList(); + foreach (var obj in removed) + entryViewModels.Remove(obj); + + foreach (var updatedEntry in updated) { - base.InsertItem(i, new EntryViewModel(entries[i])); + var match = entryViewModels.FirstOrDefault(vm => vm.Entry.Identifier == updatedEntry.Identifier); + if (match is null) // Add new Entry + entryViewModels.Add(new EntryViewModel(updatedEntry)); + else // Update Entry + match.UpdateModel(updatedEntry); } } - protected override void InsertItem(int index, EntryViewModel item) + /// + /// Updates the internal model + /// + /// The updated entry instance + public void UpdateModel(Entry entry) { - _entries.Add(item.Entry); - base.InsertItem(index, item); + Entry = entry; + CopyFromModel(); } - protected override void RemoveItem(int index) + /// + /// Updates the internal model + /// + /// The updated subentry instances + public void UpdateModel(IList entries) { - _entries.Remove(this.ElementAt(index).Entry); - base.RemoveItem(index); + MergeIntoViewModelCollection(SubEntries, entries); + CopyToModel(); + UpdateParent(); } + + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + public event PropertyChangedEventHandler PropertyChanged; } } diff --git a/src/Tests/Moryx.ClientFramework.Tests/AttributeTests.cs b/src/Tests/Moryx.ClientFramework.Tests/AttributeTests.cs index d022cd3..8a7b8ac 100644 --- a/src/Tests/Moryx.ClientFramework.Tests/AttributeTests.cs +++ b/src/Tests/Moryx.ClientFramework.Tests/AttributeTests.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0 using System; -using System.Collections.Generic; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Moryx.Container; using NUnit.Framework; +using LifeCycle = Moryx.Container.LifeCycle; namespace Moryx.ClientFramework.Tests {