diff --git a/.gitignore b/.gitignore index fde5baf94..5ccb5a940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,5 @@ -################################################################################ -# Diese .gitignore-Datei wurde von Microsoft(R) Visual Studio automatisch erstellt. -################################################################################ - -*.pfx -*.exe -*.dll +.vs +bin +obj *.bak *.user -*.bak -*.lnk -*.snk -/.svn -/.vs -/bin -/obj -/obj.net -/**/.vs -/**/obj -/**/bin -/**/TestResults -/CSharpBible/Help -/CSharpBible/Mobile -/CSharpBible/Web -/CSharpBible/WinUI -/CSharpBible/Calc/Help -/CSharpBible/DB/ADO_Test -/CSharpBible/packages -/TestStatements/packages -/CSharpProgrammierHandbuch/Fibonacci2 -/TestStatements/AsyncExampleWPF -/TestStatements/Help -/JC-AMS/JC-AMS.sln.GhostDoc.xml -/CSharpBible/Help -*.editorconfig -/CSharpBible/Libraries/CSFreeVision_ -/GenFreeWin/TestResults -/CSharpBible/Games/Sokoban_Base/*.svn -/CSharpBible/MVVM_Tutorial/UWP_00_Test/AppPackages -/TestStatements/TestStatements/Version.inc -obj.net -/JC-AMS/Core/Resource/Version.inc -/TestStatements/*.snk -/CSharpBible/Libraries/*.snk -/Transpiler_pp/Analyzer1/Analyzer1.Package/tools/install.ps1 -/Avalonia_Apps/Libraries/*.snk -/WinAhnenNew/WinAhnenClsTests/Resources/BigData5.hej diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/AA05_CommandParCalc.csproj b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/AA05_CommandParCalc.csproj index b72c495bc..b7fd17855 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/AA05_CommandParCalc.csproj +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/AA05_CommandParCalc.csproj @@ -16,7 +16,7 @@ - + diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.Designer.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.Designer.cs index 48f472329..191f18a18 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.Designer.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace AA05_CommandParCalc.Properties { // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.resx b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.resx index 76866aa6a..375341ab5 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.resx +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/Properties/Resources.resx @@ -117,47 +117,56 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 0 - 1 + @Invariant 1/x + @Invariant 2 + @Invariant 3 + @Invariant 4 + @Invariant 5 + @Invariant 6 + @Invariant 7 + @Invariant 8 + @Invariant 9 + @Invariant Actions ) + @Invariant ( + @Invariant Configuration @@ -165,24 +174,31 @@ cos + @Invariant C/CE + @Invariant . + @Invariant / + @Invariant e + @Invariant = + @Invariant + @Invariant History @@ -192,42 +208,54 @@ Inv + @Invariant log + @Invariant Macros MC + @Invariant M- + @Invariant M+ + @Invariant MR + @Invariant MS + @Invariant - + @Invariant * + @Invariant +/- + @Invariant π + @Invariant + + @Invariant Process @@ -237,14 +265,22 @@ sin + @Invariant √x + @Invariant tan + @Invariant + @Invariant + + + 0 + @Invariant \ No newline at end of file diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/ViewModels/CommandParCalcViewModel.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/ViewModels/CommandParCalcViewModel.cs index 91c9bbb46..0677403cd 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/ViewModels/CommandParCalcViewModel.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalc/ViewModels/CommandParCalcViewModel.cs @@ -37,10 +37,10 @@ public partial class CommandParCalcViewModel : ViewModelBase, ICommandParCalcVie /// /// The greeting. [ObservableProperty] - private string _greeting = "Welcome to Avalonia!"; + public partial string Greeting { get; set; } = "Welcome to Avalonia!"; [ObservableProperty] - private string _title = "Main Menu"; + public partial string Title { get; set; } = "Main Menu"; /// /// Gets the now. diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/AppTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/AppTests.cs index be70d6251..7e2a2666c 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/AppTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/AppTests.cs @@ -48,7 +48,7 @@ public void InitializeTest() public void InitDesktopTest() { // Act - Assert.ThrowsException(()=> testApp.CallInitDesktopApp()); + Assert.ThrowsExactly(()=> testApp.CallInitDesktopApp()); // Assert Assert.IsNotNull(testApp.Services); @@ -60,7 +60,7 @@ public void OnFrameworkInitializationCompletedTest() { testApp.ApplicationLifetime = Substitute.For(); // Act - Assert.ThrowsException(()=> testApp.OnFrameworkInitializationCompleted()); + Assert.ThrowsExactly(()=> testApp.OnFrameworkInitializationCompleted()); } [TestMethod()] diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Helper/AvaloniaTestMethodAttribute.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Helper/AvaloniaTestMethodAttribute.cs deleted file mode 100644 index 214ac4ad9..000000000 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Helper/AvaloniaTestMethodAttribute.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; - -namespace Avalonia.Headless.MSTest; - -/// -/// -/// This Attribute identifies a TestMethod that starts on Avalonia Dispatcher such that awaited expressions resume in the test's main thread -/// Class AvaloniaTestMethodAttribute.
This class cannot be inherited. -///
Implements the
-///
-[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] -public sealed class AvaloniaTestMethodAttribute : TestMethodAttribute -{ - public override TestResult[] Execute(ITestMethod testMethod) - { - var assembly = testMethod.MethodInfo.DeclaringType!.Assembly; - var appBuilderEntryPointType = assembly.GetCustomAttribute() - ?.AppBuilderEntryPointType; - - appBuilderEntryPointType ??= typeof(Application); - - using var _session = HeadlessUnitTestSession.StartNew(appBuilderEntryPointType); - { - return _session.Dispatch(() => ExecuteTestMethod(testMethod!), default).GetAwaiter().GetResult(); - } - } - - /// Executes the test method. - /// The test method. - /// TestResult[]. - private static TestResult[] ExecuteTestMethod(ITestMethod testMethod) - { - return [testMethod.Invoke(null)]; - } -} diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Models/CalculatorModelTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Models/CalculatorModelTests.cs index 6e5161618..35bed125c 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Models/CalculatorModelTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Models/CalculatorModelTests.cs @@ -87,7 +87,7 @@ public void SetUpTest() /// The d exp. /// As exp. /// - [DataTestMethod] + [TestMethod] [DataRow("0", new ENumbers[] { ENumbers._0 }, 0d, new string[] { "" })] [DataRow("123", new ENumbers[] { ENumbers._1, ENumbers._2, ENumbers._3 }, 123d, new string[] { @"PropChgn(CalculatorModel,Accumulator)=0 PropChg(CalculatorModel,Accumulator)=1 @@ -125,7 +125,7 @@ public void NumberCmdTest(string name, ENumbers[] aeVal, double dExp, string[] a /// The d exp. /// As exp. /// - [DataTestMethod] + [TestMethod] [DataRow("0", new ENumbers[] { ENumbers._0 }, 0d, new string[] { "" })] [DataRow("123", new ENumbers[] { ENumbers._1, ENumbers._2, ENumbers._3 }, 0.123d, new string[] { @"PropChgn(CalculatorModel,Accumulator)=0 PropChg(CalculatorModel,Accumulator)=0,1 @@ -165,7 +165,7 @@ public void NumberCmd2Test(string name, ENumbers[] aeVal, double dExp, string[] /// The d exp. /// As exp. /// - [DataTestMethod] + [TestMethod] [DataRow(EOperations.Add, 1d, 2d, 3d, new string[] { @"PropChgn(CalculatorModel,Accumulator)=0 PropChg(CalculatorModel,Accumulator)=1 PropChgn(CalculatorModel,Register)= @@ -254,7 +254,7 @@ public void OperatorCmdTest(EOperations eOp, double dVal1, double dVal2, double /// The d exp. /// As exp. /// - [DataTestMethod] + [TestMethod] [DataRow(EOperations.Negate, 1d, 2d, -1d, new string[] { @"PropChgn(CalculatorModel,Accumulator)=0 PropChg(CalculatorModel,Accumulator)=1 PropChgn(CalculatorModel,Accumulator)=1 @@ -321,7 +321,7 @@ public void OperatorCmd2Test(EOperations eOp, double dVal1, double _, double dEx /// The d a exp. /// The d m exp. /// - [DataTestMethod] + [TestMethod] [DataRow(ECommands.Nop, 123d, 123d, 123d)] [DataRow(ECommands.Clear, 123d, 0d, 123d)] [DataRow(ECommands.ClearAll, 123d, 0d, 123d)] diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ProgramTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ProgramTests.cs index 48dccc4cb..91f79a5d3 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ProgramTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ProgramTests.cs @@ -13,7 +13,7 @@ public void MainTest() try { Program.GetAppBuilder = () => AppBuilder.Configure(); - Assert.ThrowsException(()=> Program.Main(new string[] { })); + Assert.ThrowsExactly(()=> Program.Main(new string[] { })); } finally { diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewLocatorTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewLocatorTests.cs index b506f5ee8..44bc607ce 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewLocatorTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewLocatorTests.cs @@ -86,7 +86,7 @@ public void BuildTest(string sAct,string sExp) object? result = null; if (sAct.EndsWith(nameof(AA05_CommandParCalc))) { - Assert.ThrowsException(() => testClass.Build(obj)); + Assert.ThrowsExactly(() => testClass.Build(obj)); tAct = null; } else diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/CommandParCalculatorViewModelTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/CommandParCalculatorViewModelTests.cs index fa5723cb9..803b9595b 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/CommandParCalculatorViewModelTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/CommandParCalculatorViewModelTests.cs @@ -126,7 +126,7 @@ public void CommandParCalculatorViewModelTest2() /// The ad value. /// As exp. /// - [DataTestMethod()] + [TestMethod()] [DataRow("0", new double[] { 0d }, new string[] { "0", "PropChg(CommandParCalculatorViewModel,Accumulator)=0\r\n" })] [DataRow("25", new double[] { 25d }, new string[] { "25", @"PropChg(CommandParCalculatorViewModel,Accumulator)=25 " })] @@ -162,7 +162,7 @@ public void AccumulatorTest(string name, double[] adVal, string[] asExp) /// The ad value. /// As exp. /// - [DataTestMethod()] + [TestMethod()] [DataRow("0", new double[] { 0d }, new string[] { "0", @"PropChg(CommandParCalculatorViewModel,Register)=0 " })] [DataRow("25", new double[] { 25d }, new string[] { "25", @"PropChg(CommandParCalculatorViewModel,Register)=25 @@ -198,7 +198,7 @@ public void RegisterTest(string name, double[] adVal, string[] asExp) /// The ad value. /// As exp. /// - [DataTestMethod()] + [TestMethod()] [DataRow("0", new double[] { 0d }, new string[] { "0", "PropChg(CommandParCalculatorViewModel,Memory)=0\r\n" })] [DataRow("25", new double[] { 25d }, new string[] { "25", @"PropChg(CommandParCalculatorViewModel,Memory)=25 " })] @@ -225,7 +225,7 @@ public void MemoryTest(string name, double[] adVal, string[] asExp) Assert.AreEqual(asExp[1], DebugLog, "DebugOut"); } - [DataTestMethod()] + [TestMethod()] [DataRow(nameof(CommandParCalculatorViewModel.NumberCommand),ENumbers._7,nameof(ICalculatorModel.NumberCmd))] [DataRow(nameof(CommandParCalculatorViewModel.NumberCommand), ENumbers._3, nameof(ICalculatorModel.NumberCmd))] [DataRow(nameof(CommandParCalculatorViewModel.OperatorCommand), EOperations.Add, nameof(ICalculatorModel.OperatorCmd))] diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/ViewModelBaseTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/ViewModelBaseTests.cs index ec6b4b2ac..fd2c8a9cf 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/ViewModelBaseTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/ViewModels/ViewModelBaseTests.cs @@ -41,7 +41,7 @@ private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) public IRelayCommand? doSomething { get; set; } - [DataTestMethod()] + [TestMethod()] [DataRow("0 - 1 => 2",1,2,new string[] { @"OnPropChanged: o:ViewModelBaseTests, p:Property3:7 OnCanExChanged: o:RelayCommand`1 @@ -135,10 +135,10 @@ public void BaseViewModelTest(string name, int i,int iVal, string[] aExp ) [TestMethod] public void RemovePropertyDependencyTest() { - Assert.ThrowsException(() => RemovePropertyDependency("1", "3")); + Assert.ThrowsExactly(() => RemovePropertyDependency("1", "3")); } - [DataTestMethod] + [TestMethod] [DataRow(1,false)] [DataRow(2, false)] [DataRow(0, false)] @@ -147,7 +147,7 @@ public void FuncProxyTest (int dVal,bool xExp) Assert.AreEqual (xExp,FuncProxy(dVal, Prop2IsGreater)); } - [DataTestMethod] + [TestMethod] [DataRow(1, true)] [DataRow(2, true)] [DataRow(0.5, false)] diff --git a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Views/ValueConverter/DoubleValueConverterTests.cs b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Views/ValueConverter/DoubleValueConverterTests.cs index e2ebb257d..93de50a72 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Views/ValueConverter/DoubleValueConverterTests.cs +++ b/Avalonia_Apps/AA05_CommandParCalc/AA05_CommandParCalcTests/Views/ValueConverter/DoubleValueConverterTests.cs @@ -36,7 +36,7 @@ public class DoubleValueConverterTests /// The fixed factor. /// The expected result. /// - [DataTestMethod] + [TestMethod] [DataRow(123.45d, "F2", 2d, "246.90")] [DataRow(123.45d, "C2", 2d, "246.90")] [DataRow("Hallo", "C2", 2d, "Hallo")] @@ -62,7 +62,7 @@ public void ConvertTest(object value, string parameter, double fixedFactor, stri /// The fixed factor. /// The expected result. /// - [DataTestMethod] + [TestMethod] [DataRow("246.90", "F2", 2d, 123.45d)] [DataRow("246.90-", "{0}-", 2d, double.NaN)] [DataRow("246.90 ", "0.00 ", 2d, 123.45d)] diff --git a/Avalonia_Apps/AA05_CommandParCalc/Directory.Packages.props b/Avalonia_Apps/AA05_CommandParCalc/Directory.Packages.props index 8d225e499..75c20929e 100644 --- a/Avalonia_Apps/AA05_CommandParCalc/Directory.Packages.props +++ b/Avalonia_Apps/AA05_CommandParCalc/Directory.Packages.props @@ -6,22 +6,22 @@ - - + + - - - - - - - - + + + + + + + + - + - - + + \ No newline at end of file diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AA06_Converters4.csproj b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AA06_Converters4.csproj new file mode 100644 index 000000000..68ab18854 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AA06_Converters4.csproj @@ -0,0 +1,58 @@ + + + + WinExe + net8.0;net9.0 + enable + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml new file mode 100644 index 000000000..1bf62c5f9 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml.cs new file mode 100644 index 000000000..558d5becd --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.axaml.cs @@ -0,0 +1,67 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-04-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using System; +using AA06_Converters_4.Models; +using AA06_Converters_4.ViewModels; +using AA06_Converters_4.View; +using AA06_Converters_4.Models.Interfaces; + +namespace AA06_Converters_4; + +/// +/// Interaction logic for App.axaml +/// +public partial class App : Application +{ + public static IServiceProvider Services { get; private set; } = default!; + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + var services = new ServiceCollection(); + ConfigureServices(services); + Services = services.BuildServiceProvider(); + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var mainWindow = Services.GetRequiredService(); + desktop.MainWindow = mainWindow; + } + + base.OnFrameworkInitializationCompleted(); + } + + private static void ConfigureServices(IServiceCollection services) + { + // Models + services.AddSingleton(); + + // ViewModels + services.AddSingleton(); + services.AddTransient(); + + // Views + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.config b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.config new file mode 100644 index 000000000..71eaca9e5 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/App.config @@ -0,0 +1,27 @@ + + + + +
+ + + + + + 1200 + + + 2000 + + + 800 + + + -200 + + + 400 + + + + \ No newline at end of file diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AssemblyInfo.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AssemblyInfo.cs new file mode 100644 index 000000000..45651a730 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/AssemblyInfo.cs @@ -0,0 +1,23 @@ +// *********************************************************************** +// Assembly : MVVM_6_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-01-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/IMPLEMENTATION_NOTES.md b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/IMPLEMENTATION_NOTES.md new file mode 100644 index 000000000..1f01cf508 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/IMPLEMENTATION_NOTES.md @@ -0,0 +1,403 @@ +# AA06_Converters_4 - Avalonia UI Implementierung + +## Gelöste Probleme + +### 1. Encoding-Problem mit Umlauten ✅ + +**Problem:** Texte mit Umlauten (ä, ö, ü, °) wurden nicht korrekt angezeigt. + +**Lösung:** +- Alle `.axaml` Dateien wurden auf **UTF-8 mit BOM** Encoding überprüft +- `VehicleView1.axaml` korrigiert: + - "L�nge" → "Länge" + - "�" → "°" (Grad-Zeichen) + +**Empfehlung:** Stellen Sie sicher, dass alle XAML/AXAML-Dateien im UTF-8-Format gespeichert sind. + +--- + +### 2. Dynamische Grafische Darstellung ✅ + +**Problem:** Die PlotFrame-Visualisierung war leer. + +**Lösung:** Implementierung eines vollständigen **MVVM-konformen Custom Controls** für Avalonia UI. + +#### Neue Komponenten: + +##### `DynamicPlotCanvas.cs` +Ein Custom Control das: +- ✅ **Koordinatensystem** mit automatischem Grid und Achsenbeschriftung +- ✅ **AGV-Visualisierung** mit Fahrzeugkörper, Drehschemeln und Rädern +- ✅ **Interaktive Pan-Funktion** (Drag mit linker Maustaste) +- ✅ **Zoom-Funktion** (Mausrad) +- ✅ **Geschwindigkeitsvektoren** als Pfeile +- ✅ **MVVM-Bindung** über `ViewModel`-Property +- ✅ **Dependency Injection** kompatibel +- ✅ **Reaktive Updates** bei ViewModel-Änderungen +- ✅ **Isometrische Darstellung** (Längenverhältnisse richtungsunabhängig) +- ✅ **Model-Reaktivität** (reagiert auf alle Model-PropertyChanged-Events) + +--- + +### 3. Isometrische Darstellung (Längenverhältnisse) ✅ + +**Problem:** Längenverhältnisse waren richtungsabhängig (unterschiedliche Skalierung in X und Y). + +**Lösung:** Implementierung einer **isometrischen Viewport-Anpassung**: + +```csharp +private void UpdateIsometricViewport() +{ + // Berechnet einen angepassten Viewport mit gleichem Maßstab in X und Y + var aspectRatio = availableWidth / availableHeight; + var viewportAspect = viewport.Width / viewport.Height; + + if (viewportAspect > aspectRatio) + // Viewport ist breiter -> Höhe anpassen + else + // Viewport ist höher -> Breite anpassen +} +``` + +**Ergebnis:** Ein Kreis mit Radius 100 wird immer als Kreis dargestellt, unabhängig von der Fenstergröße. + +--- + +### 4. Reaktivität auf Model-Änderungen ✅ + +**Problem:** Der Plot reagierte nicht auf Änderungen im AGV-Model. + +**Lösung:** **PropertyChanged-Event-Listener** auf dem Model + ViewModel-Collections. + +**Ergebnis:** Jede Änderung an Slider-Werten wird sofort visualisiert. + +--- + +### 5. Flackern beim Drag (Pan-Funktion) ✅ + +**Problem:** Beim Verschieben mit der Maus flackerte/wackelte die Grafik. + +**Ursachen:** +1. Zu viele `InvalidateVisual()`-Aufrufe während `OnPointerMoved` +2. PropertyChanged-Events triggerten zusätzliche Renders +3. Model-Updates während Drag verursachten Render-Kaskaden + +**Lösung:** **Render-Throttling mit DispatcherTimer**: + +```csharp +// Render-Throttle-Timer (60 FPS = 16ms) +private DispatcherTimer _renderThrottleTimer = new() +{ + Interval = TimeSpan.FromMilliseconds(16) +}; + +private void ScheduleRender() +{ + if (_isRenderScheduled) return; + _isRenderScheduled = true; + + if (_isDragging) + { + // Während Dragging: Throttle auf 60 FPS + _renderThrottleTimer?.Start(); + } + else + { + // Außerhalb Dragging: Sofort rendern + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render); + } +} + +private void Model_PropertyChanged(...) +{ + // Bei Model-Änderungen nur außerhalb von Drag rendern + if (!_isDragging) + ScheduleRender(); +} +``` + +**Optimierungen:** +1. **Render-Throttling:** Max 60 FPS während Drag +2. **Model-Updates deaktiviert:** Keine Renders durch Model-Events während Drag +3. **Finales Render:** Ein sauberes Render nach Drag-Ende +4. **Flag-basierte Kontrolle:** `_isRenderScheduled` verhindert Render-Queuing + +**Ergebnis:** Butterweiche Pan-Bewegung ohne Flackern! 🎯 + +--- + +## Architektur-Highlights + +``` +┌─────────────────────────────────────┐ +│ PlotFrameViewModel │ +│ ┌───────────────────────────────┐ │ +│ │ - VPWindow (Viewport) │ │ +│ │ - WindowSize │ │ +│ │ - AGVModel (IAGVModel) │ │ +│ │ - ZoomInCommand │ │ +│ │ - ZoomOutCommand │ │ +│ │ - ResetViewCommand │ │ +│ │ - Arrows/Circles/Polygons │ │ +│ └───────────────────────────────┘ │ +└──────────────┬──────────────────────┘ + │ Data Binding + ▼ +┌─────────────────────────────────────┐ +│ DynamicPlotCanvas │ +│ (Custom Avalonia Control) │ +│ ┌───────────────────────────────┐ │ +│ │ Render Pipeline: │ │ +│ │ - UpdateIsometricViewport() │ │ +│ │ - DrawCoordinateSystem() │ │ +│ │ - DrawPolygon() (Fahrzeug) │ │ +│ │ - DrawCircle() (Drehschemel) │ │ +│ │ - DrawArrow() (Velocities) │ │ +│ └───────────────────────────────┘ │ +│ ┌───────────────────────────────┐ │ +│ │ Event Listeners: │ │ +│ │ - ViewModel.PropertyChanged │ │ +│ │ - AGVModel.PropertyChanged │ │ +│ │ - Pointer Events (Pan/Zoom) │ │ +│ └───────────────────────────────┘ │ +│ ┌───────────────────────────────┐ │ +│ │ Performance: │ │ +│ │ - Render Throttling (60 FPS) │ │ +│ │ - DispatcherTimer │ │ +│ │ - Drag-optimiert │ │ +│ └───────────────────────────────┘ │ +└─────────────────────────────────────┘ + ▲ + │ PropertyChanged Events +┌──────────────┴──────────────────────┐ +│ AGV_Model │ +│ (IAGVModel Implementation) │ +│ - VehicleDim, SwivelKoor │ +│ - Wheel Velocities, Angles │ +│ - ObservableObject │ +└─────────────────────────────────────┘ +``` + +#### Features: + +| Feature | Implementierung | Status | +|---------|----------------|--------| +| **Koordinatensystem** | Automatisches Grid mit Labels | ✅ | +| **Pan (Verschieben)** | Linke Maustaste + Drag | ✅ | +| **Pan-Performance** | 60 FPS Throttling, flackerfrei | ✅ | +| **Zoom** | Mausrad | ✅ | +| **Zoom Buttons** | Toolbar mit +/- Buttons | ✅ | +| **Reset View** | Button zum Zurücksetzen | ✅ | +| **AGV Rendering** | Fahrzeug, Drehschemel, Räder | ✅ | +| **Velocity Vectors** | Geschwindigkeitspfeile | ✅ | +| **Responsive** | Automatische Größenanpassung | ✅ | +| **MVVM-konform** | Keine View-Logik im ViewModel | ✅ | +| **DI-kompatibel** | Constructor Injection | ✅ | +| **Isometrisch** | Gleiche Skalierung X/Y | ✅ | +| **Model-Reaktiv** | Live-Updates bei Slider-Änderungen | ✅ | +| **Flackerfrei** | Render-Throttling während Drag | ✅ | + +--- + +## Technische Details + +### Performance-Optimierungen: + +1. **Render-Throttling (60 FPS)** + ```csharp + private DispatcherTimer _renderThrottleTimer = new() + { + Interval = TimeSpan.FromMilliseconds(16) // ≈ 60 FPS + }; + ``` + +2. **Flag-basierte Render-Kontrolle** + ```csharp + private bool _isRenderScheduled; + + private void ScheduleRender() + { + if (_isRenderScheduled) return; // Verhindert Render-Queuing + _isRenderScheduled = true; + // ... + } + ``` + +3. **Drag-spezifische Optimierungen** + ```csharp + private void Model_PropertyChanged(...) + { + if (!_isDragging) // Nur außerhalb Drag + ScheduleRender(); + } + ``` + +4. **Dispatcher Priority** + ```csharp + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render); + ``` + +5. **Isometrischer Viewport-Cache** + ```csharp +private RectangleF _isometricViewport; // Einmalige Berechnung pro Render + ``` + +### Threading: +- **Rendering:** UI-Thread (via `Dispatcher.UIThread`) +- **ViewModel Updates:** Jeder Thread (via `PropertyChanged`) +- **Model Updates:** Jeder Thread (via `PropertyChanged`) +- **Throttle-Timer:** UI-Thread (DispatcherTimer) + +### Rendering Pipeline: + +``` +User Input → ScheduleRender() + ↓ + _isDragging? + ↓ + YES → DispatcherTimer (16ms) → InvalidateVisual() → Render() + NO → Dispatcher.Post() → InvalidateVisual() → Render() +``` + +### Flacker-Vermeidung: + +| Problem | Lösung | +|---------|--------| +| Zu viele Renders | Throttling auf 60 FPS | +| PropertyChanged-Kaskaden | Flag `_isRenderScheduled` | +| Model-Updates während Drag | Deaktiviert via `!_isDragging` | +| Render-Queuing | Single-Flag verhindert Doppel-Renders | + +--- + +## MVVM-Best Practices + +### ✅ **Separation of Concerns** +- **Model:** `AGV_Model` (reine Datenlogik) +- **ViewModel:** `PlotFrameViewModel` (Präsentationslogik, Commands) +- **View:** `DynamicPlotCanvas` (nur Rendering und UI-Events) + +### ✅ **Dependency Injection** +```csharp +public PlotFrame(PlotFrameViewModel viewModel) +{ + DataContext = viewModel; +} +``` + +### ✅ **Observable Properties** +```csharp +[ObservableProperty] +private ArrowList _arrows; +``` + +### ✅ **Reactive Updates mit Performance** +```csharp +// ViewModel-Updates: Throttled +ViewModel.PropertyChanged += (s, e) => ScheduleRender(); + +// Model-Updates: Nur außerhalb Drag +AGVModel.PropertyChanged += (s, e) => +{ + if (!_isDragging) ScheduleRender(); +}; +``` + +### ✅ **Commands statt Event-Handler** +```csharp +[RelayCommand] +private void ZoomIn() { ... } +``` + +--- + +## Erweiterungsmöglichkeiten + +### 1. Element-Auswahl +```csharp +protected override void OnPointerPressed(...) +{ + var clickedElement = FindElementAtPosition(e.GetPosition(this)); + if (clickedElement != null) + ViewModel.SelectedElement = clickedElement; +} +``` + +### 2. Zusätzliche Shapes +```csharp +public class RectangleList : List { } +public class PathList : List { } +``` + +### 3. Export-Funktionalität +```csharp +[RelayCommand] +private async Task ExportToPng() +{ + // Render to RenderTargetBitmap +} +``` + +### 4. Snap-to-Grid +```csharp +private PointF SnapToGrid(PointF point, double gridSize) +{ + return new PointF( + (float)(Math.Round(point.X / gridSize) * gridSize), + (float)(Math.Round(point.Y / gridSize) * gridSize)); +} +``` + +--- + +## Build & Run + +```bash +dotnet build AA06_Converters_4 +dotnet run --project AA06_Converters_4 +``` + +--- + +## Tests + +```bash +dotnet test AA06_Converters_4Tests +``` + +--- + +## Bekannte Probleme + +### Design-Time-Warnung (AVLN2000) +**Symptom:** `Unable to resolve suitable regular or attached property Background` + +**Ursache:** Avalonia Designer-Bug bei Custom Controls + +**Lösung:** Ignorieren - funktioniert zur Laufzeit einwandfrei + +--- + +## Changelog + +### Version 2.0 (2024-12-20) +- ✅ **Isometrische Darstellung** implementiert +- ✅ **Model-Reaktivität** für Live-Updates +- ✅ **Anti-Flacker-Optimierung** mit Render-Throttling +- ✅ **Performance:** 60 FPS während Drag-Operationen +- ✅ **Bugfix:** Flackern beim Pan komplett eliminiert + +### Version 1.0 (2024-12-19) +- ✅ **UTF-8 Encoding** für Umlaute +- ✅ **DynamicPlotCanvas** Custom Control +- ✅ **MVVM-Architektur** mit DI +- ✅ **Pan & Zoom** Funktionalität + +--- + +## Autor +- **Mir** (Original WPF-Version) +- **GitHub Copilot** (Avalonia-Migration & Optimierungen, 2024) + +## Lizenz +Copyright © JC-Soft 2022-2024 diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml new file mode 100644 index 000000000..807c5d14d --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml.cs new file mode 100644 index 000000000..c6c51ece2 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/MainWindow.axaml.cs @@ -0,0 +1,42 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-04-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using Avalonia.Controls; +using AA06_Converters_4.View; + +namespace AA06_Converters_4; + +/// +/// Interaction logic for MainWindow.axaml +/// +public partial class MainWindow : Window +{ + /// + /// Initializes a new instance of the class. + /// + public MainWindow(VehicleView1 vehicleView, PlotFrame plotFrame) + { + InitializeComponent(); + + // Set up the content via code-behind since Avalonia doesn't support Frame navigation + var grid = this.FindControl("MainGrid"); + if (grid != null) + { + grid.Children.Add(vehicleView); + Grid.SetColumn(vehicleView, 0); + + grid.Children.Add(plotFrame); + Grid.SetColumn(plotFrame, 2); + } + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/AGV_Model.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/AGV_Model.cs new file mode 100644 index 000000000..673699e8d --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/AGV_Model.cs @@ -0,0 +1,108 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using MathLibrary.TwoDim; +using AA06_Converters_4.Properties; +using System; +using System.Collections.Generic; +using AA06_Converters_4.Models.Interfaces; + +namespace AA06_Converters_4.Models; + +public partial class AGV_Model : ObservableObject, IAGVModel +{ + // private static AGV_Model? _instance; + [ObservableProperty] + private Math2d.Vector _vehicleDim; + [ObservableProperty] +private Math2d.Vector _swivelKoor; + [ObservableProperty] + private double _axisOffset; + [ObservableProperty] + private double _swivel1Angle; + [ObservableProperty] + private double _swivel2Angle; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Swivel1Velocity))] + [NotifyPropertyChangedFor(nameof(Swivel1Rot))] + [NotifyPropertyChangedFor(nameof(AGVVelocity))] + [NotifyPropertyChangedFor(nameof(VehicleRotation))] + private double _wheel1Velocity; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Swivel1Velocity))] + [NotifyPropertyChangedFor(nameof(Swivel1Rot))] + [NotifyPropertyChangedFor(nameof(AGVVelocity))] + [NotifyPropertyChangedFor(nameof(VehicleRotation))] + private double _wheel2Velocity; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Swivel2Velocity)) + , NotifyPropertyChangedFor(nameof(Swivel2Rot)) + ,NotifyPropertyChangedFor(nameof(AGVVelocity)) + , NotifyPropertyChangedFor(nameof(VehicleRotation))] + private double _wheel3Velocity; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Swivel2Velocity)) + , NotifyPropertyChangedFor(nameof(Swivel2Rot)) + ,NotifyPropertyChangedFor(nameof(AGVVelocity)) + , NotifyPropertyChangedFor(nameof(VehicleRotation))] + private double _wheel4Velocity; + +// public static AGV_Model Instance => _instance ??= new(); + + public double Swivel1Velocity => (Wheel1Velocity + Wheel2Velocity) * 0.5d; + public double Swivel2Velocity => (Wheel3Velocity + Wheel4Velocity) * 0.5d; + public double Swivel1Rot => (Wheel2Velocity - Wheel1Velocity) / AxisOffset; + public double Swivel2Rot => (Wheel4Velocity - Wheel3Velocity) / AxisOffset; + public Math2d.Vector AGVVelocity { get => Math2d.ByLengthAngle(Swivel1Velocity,Swivel1Angle) + .Add(Math2d.ByLengthAngle(Swivel2Velocity, Swivel2Angle)) + .Mult(0.5d); } + + public double VehicleRotation => Math2d.ByLengthAngle(Swivel1Velocity, Swivel1Angle) + .Subtract(Math2d.ByLengthAngle(Swivel2Velocity, Swivel2Angle)) + .Mult(SwivelKoor.Rot90()) + / (SwivelKoor.Length()*SwivelKoor.Length()*2); + + public IEnumerable<(string, string)> Dependencies => new[]{ + (nameof(Swivel1Velocity),nameof(Wheel1Velocity)), + //(nameof(Swivel1Velocity),nameof(Wheel2Velocity)), + //(nameof(Swivel2Velocity),nameof(Wheel3Velocity)), + //(nameof(Swivel2Velocity),nameof(Wheel4Velocity)), + //(nameof(Swivel1Rot),nameof(Wheel1Velocity)), + //(nameof(Swivel1Rot),nameof(Wheel2Velocity)), + //(nameof(Swivel2Rot),nameof(Wheel3Velocity)), + //(nameof(Swivel2Rot),nameof(Wheel4Velocity)), + //(nameof(AGVVelocity),nameof(Swivel1Velocity)), + //(nameof(AGVVelocity),nameof(Swivel2Velocity)), + //(nameof(AGVVelocity),nameof(Swivel1Angle)), + //(nameof(AGVVelocity),nameof(Swivel2Angle)), +//(nameof(VehicleRotation),nameof(Swivel1Velocity)), + //(nameof(VehicleRotation),nameof(Swivel2Velocity)), + //(nameof(VehicleRotation),nameof(Swivel1Angle)), + //(nameof(VehicleRotation),nameof(Swivel2Angle)), + }; + + public bool IsDirty { get; private set; } + + public AGV_Model() + { + _vehicleDim = new Math2d.Vector(Settings.Default.Vehicle_Length, Settings.Default.Vehicle_Width); + _swivelKoor = new Math2d.Vector(Settings.Default.SwivelKoor_X, Settings.Default.SwivelKoor_Y); + _axisOffset = Settings.Default.AxisOffset; + PropertyChanged += (s, e) => IsDirty = true; + } +#if NET5_0_OR_GREATER +#else + ~AGV_Model() + { + Save(); + } +#endif + public void Save() + { + Settings.Default.Vehicle_Length = VehicleDim.x; + Settings.Default.Vehicle_Width = VehicleDim.y; + Settings.Default.SwivelKoor_X = SwivelKoor.x; + Settings.Default.SwivelKoor_Y = SwivelKoor.y; +Settings.Default.AxisOffset = AxisOffset; + Settings.Default.Save(); + IsDirty = false; + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/Interfaces/IAGVModel.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/Interfaces/IAGVModel.cs new file mode 100644 index 000000000..133474196 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Models/Interfaces/IAGVModel.cs @@ -0,0 +1,28 @@ +using MathLibrary.TwoDim; +using System.Collections.Generic; +using System.ComponentModel; + +namespace AA06_Converters_4.Models.Interfaces; + +public interface IAGVModel:INotifyPropertyChanged +{ + Math2d.Vector VehicleDim { get; set; } + Math2d.Vector SwivelKoor { get; set; } + double AxisOffset { get; set; } + double Swivel1Angle { get; set; } + double Wheel1Velocity { get; set; } + double Wheel2Velocity { get; set; } + double Swivel1Velocity { get; } + + double Swivel2Angle { get; set; } + double Wheel3Velocity { get; set; } + double Wheel4Velocity { get; set; } + double Swivel2Velocity { get; } + IEnumerable<(string Dest, string Src)> Dependencies { get; } + Math2d.Vector AGVVelocity { get; } + double VehicleRotation { get; } + double Swivel1Rot { get; } + double Swivel2Rot { get; } + + void Save(); +} \ No newline at end of file diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Program.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Program.cs new file mode 100644 index 000000000..4600586e0 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace AA06_Converters_4; + +class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. +public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() +.LogToTrace(); +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.Designer.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.Designer.cs new file mode 100644 index 000000000..b3409785e --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace AA06_Converters_4.Properties; +using System; + + +/// +/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. +/// +// Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert +// -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. +// Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen +// mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AA06_Converters_4.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.resx b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.resx new file mode 100644 index 000000000..ccbd18136 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Resources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.Designer.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.Designer.cs new file mode 100644 index 000000000..974fb994a --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.Designer.cs @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace AA06_Converters_4.Properties; + + +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] +internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1200")] + public double Vehicle_Width { + get { + return ((double)(this["Vehicle_Width"])); + } + set { + this["Vehicle_Width"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("2000")] + public double Vehicle_Length { + get { + return ((double)(this["Vehicle_Length"])); + } + set { + this["Vehicle_Length"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("800")] + public double SwivelKoor_X { + get { + return ((double)(this["SwivelKoor_X"])); + } + set { + this["SwivelKoor_X"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-200")] + public double SwivelKoor_Y { + get { + return ((double)(this["SwivelKoor_Y"])); + } + set { + this["SwivelKoor_Y"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("400")] + public double AxisOffset { + get { + return ((double)(this["AxisOffset"])); + } + set { + this["AxisOffset"] = value; + } + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.settings b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.settings new file mode 100644 index 000000000..4fae06d63 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/Properties/Settings.settings @@ -0,0 +1,21 @@ + + + + + + 1200 + + + 2000 + + + 800 + + + -200 + + + 400 + + + \ No newline at end of file diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/README.md b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/README.md new file mode 100644 index 000000000..e6848efb0 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/README.md @@ -0,0 +1,97 @@ +# AA06_Converters_4 + +> Advanced / performance notes for converters - Avalonia UI Edition + +## Overview +Converted from WPF to Avalonia UI with AGV (Automated Guided Vehicle) simulation. + +## Key Learning Goals +- Performance considerations +- Stateless design +- Testing converters +- Value converters in Avalonia +- Custom rendering with Canvas +- Dependency Injection with Microsoft.Extensions.DependencyInjection +- MVVM with CommunityToolkit.Mvvm + +## Project Structure +- Views: Avalonia XAML views (axaml) illustrating bindings and styles +- ViewModels: Reactive presentation logic using CommunityToolkit.Mvvm +- Models: Plain data / domain classes with INotifyPropertyChanged +- ValueConverter: Custom value converters for data formatting +- Services: DI-based service configuration +- Behaviors / Helpers: Reusable interaction patterns + +## Key Features +- **AGV Vehicle Simulation**: Interactive visualization of a 4-wheel automated guided vehicle +- **Real-time Calculations**: Computes vehicle velocity, rotation, and wheel dynamics +- **Value Converters**: Custom converters for units (mm, degrees, velocities) +- **Dependency Injection**: Full DI setup with constructor injection +- **Modern MVVM**: Using CommunityToolkit.Mvvm source generators + +## Namespace Migration +This project was migrated from: +- **Old namespace**: `MVVM_06_Converters_4` +- **New namespace**: `AA06_Converters_4` +- **WPF → Avalonia**: All UI components converted to Avalonia equivalents + +## Build & Run +```bash +dotnet build AA06_Converters_4 +``` + +Run: +```bash +dotnet run --project AA06_Converters_4 +``` + +## Testing +```bash +dotnet test AA06_Converters_4Tests +``` + +## Test & Coverage Status + +| Metric | Status | +|--------|--------| +| Unit Tests | Implemented (see AA06_Converters4Tests) | +| Line Coverage | TBD | +| Branch Coverage | TBD | +| Method Coverage | TBD | +| Complexity Coverage | TBD | + +### Collecting Coverage Locally + +```bash +dotnet test AA06_Converters4Tests -c Debug /p:CollectCoverage=true /p:CoverletOutputFormat=lcov \ + /p:Exclude="[xunit.*]*,[MSTest.*]*,[NUnit.*]*" --logger "trx" --results-directory ./TestResults +``` + +To merge coverage from several target frameworks: +```bash +dotnet tool install --global dotnet-reportgenerator-globaltool +reportgenerator -reports:"**/coverage.info" -targetdir:CoverageReport -reporttypes:HtmlSummary;MarkdownSummaryGithub +``` + +## Extending This Example +- Complete the PlotFrame visualization with full rendering implementation +- Add additional vehicle types with different wheel configurations +- Introduce logging / diagnostics via ILogger abstractions +- Implement animation for vehicle movement +- Add export functionality for simulation data + +## Technical Notes +- **Target Frameworks**: .NET 8.0, .NET 9.0 +- **UI Framework**: Avalonia UI 11.2.2 +- **MVVM Toolkit**: CommunityToolkit.Mvvm 8.4.0 +- **DI Container**: Microsoft.Extensions.DependencyInjection 9.0.9 +- **Math Library**: Custom MathLibrary for 2D vector calculations + +## Migration Notes +The PlotFrame visualization uses a simplified Canvas implementation. The full WPF rendering logic +with `WindowPortToGridLines` converter requires additional work to port the complex shape rendering +to Avalonia's visual system. Current implementation provides the structure with placeholders for +the full rendering implementation. + +## Notes +This README documents the Avalonia UI conversion of the original WPF project. diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/Bool2VisibilityConverter.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/Bool2VisibilityConverter.cs new file mode 100644 index 000000000..79634158d --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/Bool2VisibilityConverter.cs @@ -0,0 +1,58 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-04-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace AA06_Converters_4.ValueConverter; + +/// +/// Class Bool2VisibilityConverter. +/// Implements the +/// +/// +public class Bool2VisibilityConverter : IValueConverter +{ + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is bool x) + return x; + else + return true; + } + + /// + /// Converts a value. + /// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is bool v) + return v; + else + return false; + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/CurrencyValueConverter.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/CurrencyValueConverter.cs new file mode 100644 index 000000000..ae1aa47e1 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/CurrencyValueConverter.cs @@ -0,0 +1,60 @@ +// *********************************************************************** +// Assembly: AA06_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-04-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace AA06_Converters_4.ValueConverter; + +/// +/// Class CurrencyValueConverter. +/// Implements the +/// +/// +public class CurrencyValueConverter : IValueConverter +{ + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is decimal dval && parameter is string spar) + return dval.ToString(spar,culture); +else + return value?.ToString() ?? ""; + + } + + /// + /// Converts a value. + /// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is string sval && parameter is string spar + && decimal.TryParse(sval.Replace(spar.Substring(spar.Length - 1), "").Trim(),NumberStyles.Float, culture, out var dc)) + return dc; + else + return decimal.Zero; + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/DoubleValueConverter.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/DoubleValueConverter.cs new file mode 100644 index 000000000..93bc18471 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/ValueConverter/DoubleValueConverter.cs @@ -0,0 +1,78 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created : 07-03-2022 +// +// Last Modified By : Mir +// Last Modified On : 07-04-2022 +// *********************************************************************** +// +// Copyright (c) JC-Soft. All rights reserved. +// +// +// *********************************************************************** +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace AA06_Converters_4.ValueConverter; + +/// +/// Class CurrencyValueConverter. +/// Implements the +/// +/// +public class DoubleValueConverter : IValueConverter +{ + public double FixedFactor { get; set; } = 1.0d; + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + double dval when parameter is string spar => (dval * FixedFactor).ToString(spar, culture), + double dval => (dval * FixedFactor).ToString(culture), +_ => value?.ToString() ?? "" + }; + + } + + /// + /// Converts a value. +/// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. +/// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + string sval when + double.TryParse(sval, NumberStyles.Float, culture, out double dval) => dval / FixedFactor, + //_ when (parameter as string)?.Contains("{") == true + // => double.NaN, // Todo: + string sval when parameter is string spar && !spar.Contains("{") + => InnerParse(sval, spar), + _ => double.NaN + }; + + double InnerParse(string sval, string spar) + { + var pp = spar.LastIndexOf('0'); + if (double.TryParse(sval.Replace(spar.Substring(pp + 1), "").Trim(), NumberStyles.Float, culture, out var dVal)) + return dVal / FixedFactor; + else + return double.NaN; + + } + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Controls/DynamicPlotCanvas.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Controls/DynamicPlotCanvas.cs new file mode 100644 index 000000000..e3a5d2e57 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Controls/DynamicPlotCanvas.cs @@ -0,0 +1,633 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created : 12-20-2024 +// +// Last Modified By : Mir +// Last Modified On : 12-20-2024 +// *********************************************************************** +// +// (c) by Joe Care 2024 +// +// Dynamisches Canvas-Control fr Avalonia mit MVVM-Support +// *********************************************************************** + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Threading; +using System; +using System.Collections.ObjectModel; +using System.Drawing; // Nur fr PointF, RectangleF +using AA06_Converters_4.ViewModels; +using AvaloniaColor = Avalonia.Media.Color; // Alias fr Avalonia Color + +namespace AA06_Converters_4.View.Controls; + +/// +/// Dynamisches Canvas-Control fr Koordinatensystem-Darstellung +/// Untersttzt Pan, Zoom und dynamische Elemente via MVVM +/// +public class DynamicPlotCanvas : Control +{ + private PointF? _dragStartPos; + private RectangleF? _dragStartViewport; // Viewport beim Drag-Start + private bool _isDragging; + private RectangleF _isometricViewport; // Cache fr isometrischen Viewport + private bool _isRenderScheduled; // Flag fr Render-Throttling + private DispatcherTimer? _renderThrottleTimer; // Timer fr Render-Throttling + + /// + /// Styled Property fr das ViewModel (DataContext wird automatisch gebunden) + /// + public static readonly StyledProperty ViewModelProperty = + AvaloniaProperty.Register(nameof(ViewModel)); + + /// + /// Styled Property fr den Hintergrund + /// + public static readonly StyledProperty BackgroundProperty = + AvaloniaProperty.Register( + nameof(Background), + defaultValue: Brushes.White); + + /// + /// Gets or sets the ViewModel + /// + public PlotFrameViewModel? ViewModel + { + get => GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// Gets or sets the Background brush + /// + public IBrush? Background + { + get => GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); + } + + static DynamicPlotCanvas() + { + AffectsRender(ViewModelProperty, BoundsProperty, BackgroundProperty); + ViewModelProperty.Changed.AddClassHandler((x, e) => x.OnViewModelChanged(e)); + } + + public DynamicPlotCanvas() + { + ClipToBounds = true; + + // Render-Throttle-Timer initialisieren (16ms ? 60 FPS) + _renderThrottleTimer = new DispatcherTimer + { + Interval = TimeSpan.FromMilliseconds(16) + }; + _renderThrottleTimer.Tick += (s, e) => + { + _isRenderScheduled = false; + _renderThrottleTimer?.Stop(); + InvalidateVisual(); + }; + + // Event-Handler fr Interaktivitt + PointerPressed += OnPointerPressed; + PointerMoved += OnPointerMoved; + PointerReleased += OnPointerReleased; + PointerWheelChanged += OnPointerWheelChanged; + } + + private void OnViewModelChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is PlotFrameViewModel oldVm) + { + oldVm.PropertyChanged -= ViewModel_PropertyChanged; + + // Entferne Models-Listener + if (oldVm.AGVModel != null) + { + oldVm.AGVModel.PropertyChanged -= Model_PropertyChanged; + } + } + + if (e.NewValue is PlotFrameViewModel newVm) + { + newVm.PropertyChanged += ViewModel_PropertyChanged; + newVm.WindowSize = new Avalonia.Size(Bounds.Width, Bounds.Height); + + // Registriere Models-Listener + if (newVm.AGVModel != null) + { + newVm.AGVModel.PropertyChanged += Model_PropertyChanged; + } + } + + ScheduleRender(); + } + + private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // Whrend des Dragging nur bei bestimmten Properties rendern + if (_isDragging && e.PropertyName == nameof(PlotFrameViewModel.VPWindow)) + { + // Viewport-nderung whrend Drag -> Throttled Render + ScheduleRender(); + return; + } + + if (e.PropertyName == nameof(PlotFrameViewModel.VPWindow) || + e.PropertyName == nameof(PlotFrameViewModel.WindowPort) || + e.PropertyName == nameof(PlotFrameViewModel.Arrows) || + e.PropertyName == nameof(PlotFrameViewModel.Circles) || + e.PropertyName == nameof(PlotFrameViewModel.Polynomes)) + { + ScheduleRender(); + } + } + + private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // Bei Models-nderungen nur auerhalb von Drag-Operationen sofort rendern + if (!_isDragging) + { + ScheduleRender(); + } + } + + /// + /// Throttled Render - verhindert zu viele Render-Aufrufe + /// + private void ScheduleRender() + { + if (_isRenderScheduled) return; + + _isRenderScheduled = true; + + if (_isDragging) + { + // Whrend Dragging: Throttle auf 60 FPS + _renderThrottleTimer?.Stop(); + _renderThrottleTimer?.Start(); + } + else + { + // Auerhalb Dragging: Sofort rendern + Dispatcher.UIThread.Post(() => + { + _isRenderScheduled = false; + InvalidateVisual(); + }, DispatcherPriority.Render); + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == BoundsProperty && ViewModel != null) + { + ViewModel.WindowSize = new Avalonia.Size(Bounds.Width, Bounds.Height); + // Viewport anpassen fr isometrische Darstellung + UpdateIsometricViewport(); + } + } + + /// + /// Berechnet einen isometrischen Viewport (gleiche Skalierung in X und Y) + /// + private void UpdateIsometricViewport() + { + if (ViewModel == null || Bounds.Width == 0 || Bounds.Height == 0) + return; + + const double margin = 50; + var availableWidth = Bounds.Width - 2 * margin; + var availableHeight = Bounds.Height - 2 * margin; + + var viewport = ViewModel.VPWindow; + var aspectRatio = availableWidth / availableHeight; + var viewportAspect = viewport.Width / viewport.Height; + + RectangleF adjustedViewport; + + if (viewportAspect > aspectRatio) + { + // Viewport ist breiter -> Hhe anpassen + var newHeight = (float)(viewport.Width / aspectRatio); + var centerY = viewport.Top + viewport.Height / 2; + adjustedViewport = new RectangleF( + viewport.Left, + centerY - newHeight / 2, + viewport.Width, + newHeight); + } + else + { + // Viewport ist hher -> Breite anpassen + var newWidth = (float)(viewport.Height * aspectRatio); + var centerX = viewport.Left + viewport.Width / 2; + adjustedViewport = new RectangleF( + centerX - newWidth / 2, + viewport.Top, + newWidth, + viewport.Height); + } + + _isometricViewport = adjustedViewport; + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + if (Bounds.Width == 0 || Bounds.Height == 0) + return; + + // Hintergrund (verwendet Background-Property) + if (Background != null) + { + context.FillRectangle(Background, new Rect(Bounds.Size)); + } + + if (ViewModel == null) + return; + + // Whrend Dragging: Verwende gecachten Viewport (verhindert Wackeln) + // Auerhalb Dragging: Berechne isometrischen Viewport neu + if (!_isDragging) + { + UpdateIsometricViewport(); + } + + var viewport = _isometricViewport; + var windowPort = ViewModel.WindowPort; + + // Zeichne Koordinatensystem + DrawCoordinateSystem(context, viewport, windowPort); + + // Zeichne Polygone (Fahrzeug) + if (ViewModel.Polynomes?.Count > 0) + { + foreach (var poly in ViewModel.Polynomes) + { + DrawPolygon(context, viewport, poly); + } + } + + // Zeichne Kreise (Drehschemel) + if (ViewModel.Circles?.Count > 0) + { + foreach (var circle in ViewModel.Circles) + { + DrawCircle(context, viewport, circle); + } + } + + // Zeichne Pfeile (Geschwindigkeiten) + if (ViewModel.Arrows?.Count > 0) + { + foreach (var arrow in ViewModel.Arrows) + { + DrawArrow(context, viewport, arrow); + } + } + + // Zeichne AGV (wenn vorhanden und nicht schon durch Polygone gezeichnet) + if (ViewModel.AGVModel != null && (ViewModel.Polynomes == null || ViewModel.Polynomes.Count == 0)) + { + DrawAGV(context, viewport); + } + } + + private void DrawPolygon(DrawingContext context, RectangleF viewport, PolynomeData poly) + { + if (poly.Points == null || poly.Points.Count < 2) + return; + + var points = new Avalonia.Point[poly.Points.Count]; + for (int i = 0; i < poly.Points.Count; i++) + { + points[i] = Real2Vis(poly.Points[i], viewport); + } + + var geometry = new PolylineGeometry(points, true); + var brush = ViewModel?.Polynomes?.PenData?.Item1 ?? Brushes.Blue; + var thickness = ViewModel?.Polynomes?.PenData?.Item2 ?? 2.0; + + context.DrawGeometry(null, new Pen(brush, thickness), geometry); + } + + private void DrawCircle(DrawingContext context, RectangleF viewport, CircleData circle) + { + var center = Real2Vis(circle.Center, viewport); + + // Radius isometrisch umrechnen (durchschnittliche Skalierung) + var radiusPoint = new PointF(circle.Center.X + (float)circle.Radius, circle.Center.Y); + var radiusVis = Real2Vis(radiusPoint, viewport); + var radius = Math.Abs(radiusVis.X - center.X); + + var brush = ViewModel?.Circles?.PenData?.Item1 ?? Brushes.Green; + var thickness = ViewModel?.Circles?.PenData?.Item2 ?? 1.0; + + context.DrawEllipse(null, new Pen(brush, thickness), center, radius, radius); + } + + private void DrawArrow(DrawingContext context, RectangleF viewport, ArrowData arrow) + { + var start = Real2Vis(arrow.Start, viewport); + var end = Real2Vis(arrow.End, viewport); + + var brush = ViewModel?.Arrows?.PenData?.Item1 ?? Brushes.Red; + var thickness = ViewModel?.Arrows?.PenData?.Item2 ?? 2.0; + + // Hauptlinie + context.DrawLine(new Pen(brush, thickness), start, end); + + // Pfeilspitze + DrawArrowHead(context, start, end, brush, thickness); + } + + private void DrawCoordinateSystem(DrawingContext context, RectangleF viewport, SWindowPort windowPort) + { + var pen = new Pen(Brushes.LightGray, 0.5); + var axisPen = new Pen(Brushes.Black, 1.5); + var textBrush = Brushes.Black; + + // Berechne Schrittweite fr Gitterlinien + double gridStep = CalculateGridStep(viewport.Width, viewport.Height); + + // Vertikale Gitterlinien + double startX = Math.Floor(viewport.Left / gridStep) * gridStep; + for (double x = startX; x <= viewport.Right; x += gridStep) + { + var p1 = Real2Vis(new PointF((float)x, viewport.Top), viewport); + var p2 = Real2Vis(new PointF((float)x, viewport.Bottom), viewport); + + // Koordinatenachse dicker + var currentPen = Math.Abs(x) < gridStep / 10 ? axisPen : pen; + context.DrawLine(currentPen, p1, p2); + + // Beschriftung + if (Math.Abs(x) > gridStep / 10) + { + var text = new FormattedText( + $"{x:0}", + System.Globalization.CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface("Arial"), + 10, + textBrush); + + context.DrawText(text, new Avalonia.Point(p1.X - text.Width / 2, Bounds.Height - 15)); + } + } + + // Horizontale Gitterlinien + double startY = Math.Floor(viewport.Top / gridStep) * gridStep; + for (double y = startY; y <= viewport.Bottom; y += gridStep) + { + var p1 = Real2Vis(new PointF(viewport.Left, (float)y), viewport); + var p2 = Real2Vis(new PointF(viewport.Right, (float)y), viewport); + + var currentPen = Math.Abs(y) < gridStep / 10 ? axisPen : pen; + context.DrawLine(currentPen, p1, p2); + + // Beschriftung + if (Math.Abs(y) > gridStep / 10) + { + var text = new FormattedText( + $"{y:0}", + System.Globalization.CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface("Arial"), + 10, + textBrush); + + context.DrawText(text, new Avalonia.Point(5, p1.Y - text.Height / 2)); + } + } + + // Nullpunkt markieren + var origin = Real2Vis(new PointF(0, 0), viewport); + context.DrawEllipse(Brushes.Red, new Pen(Brushes.DarkRed, 2), origin, 5, 5); + } + + private void DrawAGV(DrawingContext context, RectangleF viewport) + { + if (ViewModel?.AGVModel == null) + return; + + var agv = ViewModel.AGVModel; + var vehicleDim = agv.VehicleDim; + var swivelKoor = agv.SwivelKoor; + + // Zeichne Fahrzeug-Rechteck (zentriert am Ursprung) + var halfWidth = (float)(vehicleDim.x / 2); + var halfHeight = (float)(vehicleDim.y / 2); + + var corners = new[] + { + Real2Vis(new PointF(-halfWidth, -halfHeight), viewport), + Real2Vis(new PointF(halfWidth, -halfHeight), viewport), + Real2Vis(new PointF(halfWidth, halfHeight), viewport), + Real2Vis(new PointF(-halfWidth, halfHeight), viewport) + }; + + var geometry = new PolylineGeometry(corners, true); + context.DrawGeometry(new SolidColorBrush(AvaloniaColor.FromArgb(100, 0, 100, 255)), + new Pen(Brushes.Blue, 2), geometry); + + // Zeichne Drehschemel + DrawSwivel(context, viewport, new PointF((float)swivelKoor.x, (float)swivelKoor.y), agv.Swivel1Angle, agv.AxisOffset); + DrawSwivel(context, viewport, new PointF((float)-swivelKoor.x, (float)-swivelKoor.y), agv.Swivel2Angle, agv.AxisOffset); + + // Zeichne Geschwindigkeitsvektor + DrawVelocityVector(context, viewport, agv.AGVVelocity); + } + + private void DrawSwivel(DrawingContext context, RectangleF viewport, + PointF position, double angle, double axisOffset) + { + var center = Real2Vis(position, viewport); + + // Drehschemel-Kreis + context.DrawEllipse(Brushes.LightBlue, new Pen(Brushes.DarkBlue, 1.5), center, 8, 8); + + // Ausrichtungs-Pfeil + var endX = position.X + (float)(Math.Cos(angle) * 50); + var endY = position.Y + (float)(Math.Sin(angle) * 50); + var endPoint = Real2Vis(new PointF(endX, endY), viewport); + + context.DrawLine(new Pen(Brushes.DarkBlue, 2), center, endPoint); + + // Rder + var wheelHalfOffset = axisOffset / 2; + var perpX = -Math.Sin(angle); + var perpY = Math.Cos(angle); + + var wheel1Pos = new PointF( + position.X + (float)(perpX * wheelHalfOffset), + position.Y + (float)(perpY * wheelHalfOffset)); + var wheel2Pos = new PointF( + position.X - (float)(perpX * wheelHalfOffset), + position.Y - (float)(perpY * wheelHalfOffset)); + + context.DrawEllipse(Brushes.Black, null, Real2Vis(wheel1Pos, viewport), 4, 4); + context.DrawEllipse(Brushes.Black, null, Real2Vis(wheel2Pos, viewport), 4, 4); + } + + private void DrawVelocityVector(DrawingContext context, RectangleF viewport, + MathLibrary.TwoDim.Math2d.Vector velocity) + { + if (velocity.Length() < 0.1) return; + + var origin = Real2Vis(new PointF(0, 0), viewport); + var endPoint = Real2Vis(new PointF((float)velocity.x / 10, (float)velocity.y / 10), viewport); + + var pen = new Pen(Brushes.Green, 3); + context.DrawLine(pen, origin, endPoint); + + // Pfeilspitze + DrawArrowHead(context, origin, endPoint, Brushes.Green, 3); + } + + private void DrawArrowHead(DrawingContext context, Avalonia.Point start, Avalonia.Point end, IBrush brush, double thickness = 3) + { + const double arrowLength = 10; + const double arrowAngle = Math.PI / 6; + + var dx = end.X - start.X; + var dy = end.Y - start.Y; + var angle = Math.Atan2(dy, dx); + + var p1 = new Avalonia.Point( + end.X - arrowLength * Math.Cos(angle - arrowAngle), + end.Y - arrowLength * Math.Sin(angle - arrowAngle)); + + var p2 = new Avalonia.Point( + end.X - arrowLength * Math.Cos(angle + arrowAngle), + end.Y - arrowLength * Math.Sin(angle + arrowAngle)); + + var geometry = new PolylineGeometry(new[] { p1, end, p2 }, false); + context.DrawGeometry(null, new Pen(brush, thickness), geometry); + } + + private double CalculateGridStep(double width, double height) + { + double maxDim = Math.Max(width, height); + double baseStep = Math.Pow(10, Math.Floor(Math.Log10(maxDim))); + + if (maxDim / baseStep < 3) + return baseStep / 5; + else if (maxDim / baseStep < 6) + return baseStep / 2; + else + return baseStep; + } + + private Avalonia.Point Real2Vis(PointF realPoint, RectangleF viewport) + { + const double margin = 50; // Rand fr Beschriftungen + + var x = margin + (realPoint.X - viewport.Left) * (Bounds.Width - 2 * margin) / viewport.Width; + var y = Bounds.Height - margin - (realPoint.Y - viewport.Top) * (Bounds.Height - 2 * margin) / viewport.Height; + + return new Avalonia.Point(x, y); + } + + private PointF Vis2Real(Avalonia.Point visPoint, RectangleF viewport) + { + const double margin = 50; + + var x = viewport.Left + (visPoint.X - margin) * viewport.Width / (Bounds.Width - 2 * margin); + var y = viewport.Top + (Bounds.Height - margin - visPoint.Y) * viewport.Height / (Bounds.Height - 2 * margin); + + return new PointF((float)x, (float)y); + } + + // Interaktivitt + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (ViewModel == null) return; + + var point = e.GetCurrentPoint(this); + if (point.Properties.IsLeftButtonPressed) + { + // Speichere Drag-Start in REAL-Koordinaten (basierend auf aktuellem isometrischen Viewport) + _dragStartPos = Vis2Real(point.Position, _isometricViewport); + _dragStartViewport = ViewModel.VPWindow; + _isDragging = true; + e.Handled = true; + } + } + + private void OnPointerMoved(object? sender, PointerEventArgs e) + { + if (!_isDragging || ViewModel == null || _dragStartPos == null || _dragStartViewport == null) + return; + + // Berechne aktuelle Position in REAL-Koordinaten + var currentPos = Vis2Real(e.GetPosition(this), _isometricViewport); + + // Berechne Delta in REAL-Koordinaten + var deltaX = _dragStartPos.Value.X - currentPos.X; + var deltaY = _dragStartPos.Value.Y - currentPos.Y; + + // Verschiebe den URSPRNGLICHEN Viewport (nicht den aktuellen!) + // Das verhindert das Akkumulieren von Rundungsfehlern + var newViewport = new RectangleF( + _dragStartViewport.Value.Left + deltaX, + _dragStartViewport.Value.Top + deltaY, + _dragStartViewport.Value.Width, + _dragStartViewport.Value.Height); + + // Setze neuen Viewport + ViewModel.VPWindow = newViewport; + + // Aktualisiere isometrischen Viewport fr diesen Frame + // (aber nur die Translation, nicht die Grenanpassung) + _isometricViewport = new RectangleF( + _isometricViewport.Left + deltaX, + _isometricViewport.Top + deltaY, + _isometricViewport.Width, + _isometricViewport.Height); + + e.Handled = true; + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + _isDragging = false; + _dragStartPos = null; + _dragStartViewport = null; + + // Nach Dragging: Isometrischen Viewport neu berechnen und ein finales Render + UpdateIsometricViewport(); + _isRenderScheduled = false; + InvalidateVisual(); + } + + private void OnPointerWheelChanged(object? sender, PointerWheelEventArgs e) + { + if (ViewModel == null) return; + + var viewport = ViewModel.VPWindow; + var zoomFactor = e.Delta.Y > 0 ? 0.9f : 1.1f; + + var newWidth = viewport.Width * zoomFactor; + var newHeight = viewport.Height * zoomFactor; + + var centerX = viewport.Left + viewport.Width / 2; + var centerY = viewport.Top + viewport.Height / 2; + + ViewModel.VPWindow = new RectangleF( + centerX - newWidth / 2, + centerY - newHeight / 2, + newWidth, + newHeight); + + e.Handled = true; + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Converter/WindowPortToGridLines.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Converter/WindowPortToGridLines.cs new file mode 100644 index 000000000..61d53d238 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/Converter/WindowPortToGridLines.cs @@ -0,0 +1,427 @@ +// *********************************************************************** +// Assembly : AA06_Converters_4 +// Author : Mir +// Created: 08-28-2022 +// +// Last Modified By : Mir +// Last Modified On : 08-29-2022 +// *********************************************************************** +// +// (c) by Joe Care 2022 +// +// +// *********************************************************************** +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using AA06_Converters_4.ViewModels; +using System.Collections.ObjectModel; +using Avalonia.Data.Converters; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Layout; + +namespace AA06_Converters_4.View.Converter; + +/// +/// Class WindowPortToGridLines. +/// Implements the +/// +/// +public class WindowPortToGridLines : IValueConverter +{ + /// + /// The average GRD pixel + /// + const int AvgGrdPixel = 40; + + /// + /// The lb (Label-Bereich) + /// + Avalonia.Size lb = new Avalonia.Size(50, 28); + + /// + /// Gets or sets the size of the window. + /// + /// The size of the window. + public Avalonia.Size WindowSize { get; set; } = new Avalonia.Size(600, 600); + + /// + /// Real2s the vis. + /// + /// The value. + /// The vis minimum. + /// The vis maximum. + /// The r minimum. + /// The r maximum. + /// System.Double. + private double Real2Vis(double value, double visMin, double visMax, double rMin, double rMax) + => visMin + (value - rMin) * (visMax - visMin) / (rMax - rMin); + + /// + /// Real2s the vis p. + /// + /// The value. + /// The port. + /// Avalonia.Point. + private Avalonia.Point real2VisP(PointF value, RectangleF port) => + new Avalonia.Point( + Real2Vis(value.X, 0, WindowSize.Width - lb.Width, port.Left, port.Right) + lb.Width, + Real2Vis(value.Y, WindowSize.Height - lb.Height, 0d, port.Top, port.Bottom) + lb.Height); + + /// + /// Vis2s the real p. + /// + /// The value. + /// The port. + /// PointF. + private PointF vis2RealP(Avalonia.Point value, RectangleF port) => + new PointF( + (float)Real2Vis(value.X - lb.Width, port.Left, port.Right, 0, WindowSize.Width - lb.Width), + (float)Real2Vis(value.Y - lb.Height, port.Top, port.Bottom, WindowSize.Height - lb.Height, 0d)); + + /// + /// The real2 vis p + /// + public Func Real2VisP; + + /// + /// The vis2 real p + /// + public Func Vis2RealP; + + private RectangleF actPort; + + /// + /// Initializes a new instance of the class. + /// + public WindowPortToGridLines() + { + Real2VisP = real2VisP; + Vis2RealP = vis2RealP; + } + + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + ObservableCollection result; + switch (value) + { + case SWindowPort c: + var b = new SolidColorBrush(Colors.Black); + + var hOffset = 0d; + RectangleF port2; // erweiterter Viewport +port2 = GetAdjustedRect(c); + + double BigStep, Step; + ComputeGridSteps(port2.Width, out BigStep, out Step); + + double MinStepX = Math.Ceiling(port2.Left / Step) * Step; + double MinStepY = Math.Ceiling(port2.Top / Step) * Step; + + actPort = port2; + result = new ObservableCollection(); + + if (c.port.Contains(PointF.Empty)) + { + var p = Real2VisP(PointF.Empty, port2); + Ellipse el = new Ellipse() + { + Height = 7, + Width = 7, + Margin = new Thickness(p.X - 3, p.Y - 3, 0, 0), +Stroke = b, + StrokeThickness = 0.3d + }; + result.Add(el); + } + + for (var i = 0; MinStepX + i * Step < port2.Right || MinStepY + i * Step < port2.Bottom; i++) + { + double X1 = MinStepX + i * Step; + var P1x = real2VisP(new PointF((float)X1, port2.Top), port2); + var P2x = real2VisP(new PointF((float)X1, port2.Bottom), port2); + if (P1x.X < WindowSize.Width) + { +result.Add(CreateLine(b, GetStroke(X1, Step, BigStep), P1x, P2x)); + + if (Math.Abs((Math.Abs(X1) + Step / 5) % BigStep) < Step / 2) + { + result.Add(CreateLabel(X1, + new Thickness((double)(P1x.X - lb.Width / 2d + 7), 0d, 0d, 0d), + VerticalAlignment.Bottom, + HorizontalAlignment.Center)); + } + } + + double Y1 = MinStepY + i * Step; + var P1y = real2VisP(new PointF(port2.Left, (float)Y1), port2); + var P2y = real2VisP(new PointF(port2.Right, (float)Y1), port2); + if (P1y.Y < WindowSize.Height - hOffset && P1y.Y > lb.Height) + { + result.Add(CreateLine(b, GetStroke(Y1, Step, BigStep), P1y, P2y)); + if (Math.Abs((Math.Abs(Y1) + Step / 5) % BigStep) < Step / 2) + { + result.Add(CreateLabel(Y1, + new Thickness(0d, (double)(P1y.Y - hOffset - lb.Height + 5), 0d, 0d), + VerticalAlignment.Center, + HorizontalAlignment.Right)); + } + } + } + return result; + + case DataSet ds: + result = new ObservableCollection(); + if (ds.Datapoints?.Length > 1) + { + for (int i = 0; i < ds.Datapoints.Length - 1; i++) + { + var P1 = real2VisP(ds.Datapoints[i], actPort); + var P2 = real2VisP(ds.Datapoints[i + 1], actPort); + result.Add(CreateLine(ds.Pen.Brush, ds.Pen.Thickness, P1, P2)); + } + } + return result; + + case DataSet[] ads: + return new ObservableCollection(); + + case ArrowList al: + result = new ObservableCollection(); + foreach (var sh in al) + { + var P1 = real2VisP(sh.Start, actPort); + var P2 = real2VisP(sh.End, actPort); + foreach (var el in CreateArrow(al.PenData?.Item1, al.PenData?.Item2 ?? 1, P1, P2)) + result.Add(el); + } + return result; + + case CircleList cl: + result = new ObservableCollection(); + foreach (var sh in cl) + { +var P1 = real2VisP(sh.Center, actPort); + var r = Real2VisP(PointF.Add(sh.Center, new SizeF((float)sh.Radius, 0)), actPort).X - P1.X; + result.Add(CreateCircle(cl.PenData?.Item1, cl.PenData?.Item2 ?? 1, P1, (float)r)); + } + return result; + + case PolynomeList pl: + result = new ObservableCollection(); + foreach (var sh in pl) + { + var p = new List(); + foreach (var pnt in sh.Points) + p.Add(real2VisP(pnt, actPort)); + result.Add(CreatePolynome(pl.PenData?.Item1, pl.PenData?.Item2 ?? 1, p)); + } + return result; + + default: + return new ObservableCollection(); + } + + double GetStroke(double X1, double Step, double BigStep) + { + switch (X1) + { + case double when Math.Abs(X1) < Step / 5: + return 1.5d; + case double when Math.Abs((Math.Abs(X1) + Step / 5) % BigStep) < Step / 2: + return 0.8d; + default: +return 0.3d; + } + } + } + + /// + /// Gets the adjusted rect. + /// + /// The c. + /// RectangleF. + public RectangleF GetAdjustedRect(SWindowPort c) + { + RectangleF port2; + if (Math.Abs(WindowSize.Width * c.port.Height) < Math.Abs(WindowSize.Height * c.port.Width)) + port2 = new RectangleF( + c.port.Left, + (float)(c.port.Top + c.port.Height * 0.5f - (WindowSize.Height * 0.5f) * c.port.Width / (float)WindowSize.Width), + c.port.Width, + (float)(c.WindowSize.Height * c.port.Width / WindowSize.Width)); + else + port2 = new RectangleF( + (float)(c.port.Left + c.port.Width * 0.5 - (WindowSize.Width / 2) * c.port.Height / WindowSize.Height), + c.port.Top, + (float)(WindowSize.Width * c.port.Height / WindowSize.Height), + c.port.Height); + return port2; + } + + /// + /// Creates a line. + /// + /// The brush. + /// The stroke thickness. + /// The start point. + /// The end point. + /// Control. + Control CreateLine(IBrush? b, double value, Avalonia.Point P1, Avalonia.Point P2) + { + return new Line() +{ + StartPoint = P1, + EndPoint = P2, + Stroke = b ?? Brushes.Black, + StrokeThickness = value + }; + } + + /// + /// Creates a circle. + /// + /// The brush. + /// The stroke thickness. + /// The center point. + /// The radius. + /// Control. + Control CreateCircle(IBrush? b, double value, Avalonia.Point P1, float r) + { + return new Ellipse() + { + Margin = new Thickness(P1.X - r, P1.Y - r, 0, 0), + Height = r * 2, + Width = r * 2, + Stroke = b ?? Brushes.Black, + StrokeThickness = value + }; + } + + /// + /// Creates a polynome/polygon. + /// + /// The brush. + /// The stroke thickness. + /// The points. + /// Control. + Control CreatePolynome(IBrush? b, double value, List Pts) + { + return new Polygon() + { + Points = new Points(Pts), + Stroke = b ?? Brushes.Black, + StrokeThickness = value + }; + } + + /// + /// Creates an arrow (line with arrowhead). + /// + /// The brush. + /// The stroke thickness. + /// The start point. + /// The end point. + /// Enumerable of controls. + IEnumerable CreateArrow(IBrush? b, double value, Avalonia.Point P1, Avalonia.Point P2) + { + // Hauptlinie + yield return new Line() + { + StartPoint = P1, + EndPoint = P2, + Stroke = b ?? Brushes.Black, + StrokeThickness = value, +}; + + // Pfeilspitze + System.Numerics.Vector2 v = new((float)(P2.X - P1.X), (float)(P2.Y - P1.Y)); + var l = v.Length(); + if (l > 0) + { + var ve = v * (1 / l); + yield return new Line() +{ + StartPoint = new Avalonia.Point(P2.X - ve.X * (value * 2), P2.Y - ve.Y * (value * 2)), + EndPoint = new Avalonia.Point(P2.X - ve.X * value, P2.Y - ve.Y * value), + Stroke = b ?? Brushes.Black, + StrokeThickness = value * 5, + StrokeLineCap = PenLineCap.Round // Avalonia hat kein Triangle, Round ist ähnlich + }; + } + } + + /// + /// Creates a label. + /// + /// The value to display. + /// The margin. + /// The vertical alignment. + /// The horizontal alignment. + /// Control. + Control CreateLabel(double Y1, Thickness margin, VerticalAlignment va, HorizontalAlignment ha) + { + return new TextBlock() + { + Text = $"{Y1:0.###}", + Width = lb.Width, + Height = lb.Height, + VerticalAlignment = va, + HorizontalAlignment = ha, + Margin = margin + }; + } + + /// + /// Computes the grid steps. + /// + /// Width of the viewport. + /// The big step. + /// The step. + private void ComputeGridSteps(double vpWidth, out double BigStep, out double Step) + { + BigStep = Math.Pow(10, Math.Floor(Math.Log10(vpWidth * AvgGrdPixel * 10 / WindowSize.Width))); + while ((BigStep / vpWidth * WindowSize.Width) < AvgGrdPixel * 3 / 1.5) + BigStep *= 2d; + while ((BigStep / vpWidth * WindowSize.Width) > AvgGrdPixel * 4.5) + BigStep *= 0.5d; + switch ((BigStep / vpWidth * WindowSize.Width) / AvgGrdPixel) + { + case double d when d > 3.5: + Step = BigStep / 10; +break; + case double d when d < 2.5: + Step = BigStep / 4; + break; + default: + Step = BigStep / 5; + break; + } + } + + /// + /// Converts a value back (not implemented). + /// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. +/// A converted value. + /// + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/PlotFrame.axaml b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/PlotFrame.axaml new file mode 100644 index 000000000..43ad203a7 --- /dev/null +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_Converters4/View/PlotFrame.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + +
/// The greeting. [ObservableProperty] - private string _greeting = "Welcome to Avalonia!"; + public partial string Greeting { get; set; } = "Welcome to Avalonia!"; [ObservableProperty] - private string _title = "Main Menu"; + public partial string Title { get; set; } = "Main Menu"; /// /// Gets the now. @@ -50,7 +50,7 @@ public partial class ValueConverterViewModel : ViewModelBase, IValueConverterVie public DateTime Now => _model.Now; [ObservableProperty] - private double _inputValue; + public partial double InputValue { get; set; } public double ResultValue => _model.ResultValue; /// diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/AppTests.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/AppTests.cs index 1ba9c79ea..aea3c85a1 100644 --- a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/AppTests.cs +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/AppTests.cs @@ -48,7 +48,7 @@ public void InitializeTest() public void InitDesktopTest() { // Act - Assert.ThrowsException(()=> testApp.CallInitDesktopApp()); + Assert.ThrowsExactly(()=> testApp.CallInitDesktopApp()); // Assert Assert.IsNotNull(testApp.Services); @@ -60,7 +60,7 @@ public void OnFrameworkInitializationCompletedTest() { testApp.ApplicationLifetime = Substitute.For(); // Act - Assert.ThrowsException(()=> testApp.OnFrameworkInitializationCompleted()); + Assert.ThrowsExactly(()=> testApp.OnFrameworkInitializationCompleted()); } [TestMethod()] diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/Helper/AvaloniaTestMethodAttribute.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/Helper/AvaloniaTestMethodAttribute.cs index 214ac4ad9..4c2ea5337 100644 --- a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/Helper/AvaloniaTestMethodAttribute.cs +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/Helper/AvaloniaTestMethodAttribute.cs @@ -11,7 +11,7 @@ namespace Avalonia.Headless.MSTest; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public sealed class AvaloniaTestMethodAttribute : TestMethodAttribute { - public override TestResult[] Execute(ITestMethod testMethod) + public override async Task ExecuteAsync(ITestMethod testMethod) { var assembly = testMethod.MethodInfo.DeclaringType!.Assembly; var appBuilderEntryPointType = assembly.GetCustomAttribute() @@ -28,8 +28,8 @@ public override TestResult[] Execute(ITestMethod testMethod) /// Executes the test method. /// The test method. /// TestResult[]. - private static TestResult[] ExecuteTestMethod(ITestMethod testMethod) + private static async Task ExecuteTestMethod(ITestMethod testMethod) { - return [testMethod.Invoke(null)]; + return [await testMethod.InvokeAsync(null).ConfigureAwait(false)]; } } diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ProgramTests.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ProgramTests.cs index c0119476b..a92293cf6 100644 --- a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ProgramTests.cs +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ProgramTests.cs @@ -13,7 +13,7 @@ public void MainTest() try { Program.GetAppBuilder = () => AppBuilder.Configure(); - Assert.ThrowsException(()=> Program.Main(new string[] { })); + Assert.ThrowsExactly(()=> Program.Main(new string[] { })); } finally { diff --git a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ViewLocatorTests.cs b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ViewLocatorTests.cs index b24f57a59..5fc066b13 100644 --- a/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ViewLocatorTests.cs +++ b/Avalonia_Apps/AA06_ValueConverter2/AA06_ValueConverter2Tests/ViewLocatorTests.cs @@ -90,7 +90,7 @@ public void BuildTest(string sAct,string sExp) object? result = null; if (sAct.EndsWith(nameof(AA06_ValueConverter2))) { - Assert.ThrowsException(() => testClass.Build(obj)); + Assert.ThrowsExactly(() => testClass.Build(obj)); tAct = null; } else diff --git a/Avalonia_Apps/AA06_ValueConverter2/Directory.Packages.props b/Avalonia_Apps/AA06_ValueConverter2/Directory.Packages.props index 8d225e499..031d8b4f7 100644 --- a/Avalonia_Apps/AA06_ValueConverter2/Directory.Packages.props +++ b/Avalonia_Apps/AA06_ValueConverter2/Directory.Packages.props @@ -6,22 +6,26 @@ - - + + + + - - - - - - - - + + + + + + + + - + + - - + + + \ No newline at end of file diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj new file mode 100644 index 000000000..edeefc7f9 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj @@ -0,0 +1,64 @@ + + + + WinExe + net8.0;net9.0 + true + + + + + enable + disable + + + + + + + + + + + + + + + None + All + + + + + + + + + + + + + True + True + Resources.resx + + + DialogView.axaml + + + MainWindow.axaml + + + DialogWindow.axaml + + + App.axaml + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj.user b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj.user new file mode 100644 index 000000000..a32401905 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/AA09_DialogBoxes.csproj.user @@ -0,0 +1,9 @@ + + + + + + Designer + + + \ No newline at end of file diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml new file mode 100644 index 000000000..87219e96d --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml.cs new file mode 100644 index 000000000..056048e1d --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.axaml.cs @@ -0,0 +1,38 @@ +// *********************************************************************** +// Assembly : AA09_DialogBoxes +// Author : Mir +// Created : 12-29-2021 +// +// Last Modified By : Mir +// Last Modified On : 12-29-2021 +// *********************************************************************** +// +// Copyright © JC-Soft 2021 +// +// +// *********************************************************************** +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace AA09_DialogBoxes; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new Views.MainWindow + { + }; + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.config b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.config new file mode 100644 index 000000000..9b6bf3fe6 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/EditDialogRequestMessage.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/EditDialogRequestMessage.cs new file mode 100644 index 000000000..5a49098cd --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/EditDialogRequestMessage.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace AA09_DialogBoxes.Messages; + +public sealed class EditDialogRequestMessage : AsyncRequestMessage<(bool,(string,string))> +{ + public string Name { get; } + public string Email { get; } + + public EditDialogRequestMessage(string name, string email) + { + Name = name; + Email = email; + } +} diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/MessageBoxRequestMessage.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/MessageBoxRequestMessage.cs new file mode 100644 index 000000000..fe86a4a9f --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Messages/MessageBoxRequestMessage.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging.Messages; +using AA09_DialogBoxes.ViewModels; +using MsBox.Avalonia.Enums; + +namespace AA09_DialogBoxes.Messages; + +public sealed class MessageBoxRequestMessage : AsyncRequestMessage +{ + public string Title { get; } + public string Content { get; } + public MessageBoxRequestMessage(string title, string content) + { + Title = title; + Content = content; + } +} diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Program.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Program.cs new file mode 100644 index 000000000..f1b6c2d7c --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Program.cs @@ -0,0 +1,33 @@ +using Avalonia; +using System; + +namespace AA09_DialogBoxes; + +internal class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + public static void Main(string[] args) + => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + /// + /// Builds the avalonia application. + /// + /// AppBuilder. + public static AppBuilder BuildAvaloniaApp() //{ get; set; } = () + => GetAppBuilder(); + + /// + /// Builds the avalonia application. + /// + /// AppBuilder. + public static Func GetAppBuilder { get; set; } = () + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +// .UseReactiveUI(); +} diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/AssemblyInfo.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..366dd6da1 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/AssemblyInfo.cs @@ -0,0 +1,68 @@ +// *********************************************************************** +// Assembly : MVVM_09a_CTDialogBoxes +// Author : Mir +// Created : 12-29-2021 +// +// Last Modified By : Mir +// Last Modified On : 12-29-2021 +// *********************************************************************** +// +// Copyright © JC-Soft 2021 +// +// +// *********************************************************************** +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("MVVM_09a_CTDialogBoxes")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("JC-Soft")] +[assembly: AssemblyProduct("MVVM_09a_CTDialogBoxes")] +[assembly: AssemblyCopyright("Copyright © JC-Soft 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +//Um mit dem Erstellen lokalisierbarer Anwendungen zu beginnen, legen Sie +//ImCodeVerwendeteKultur in der .csproj-Datei +//in einer fest. Wenn Sie in den Quelldateien beispielsweise Deutsch +//(Deutschland) verwenden, legen Sie auf \"de-DE\" fest. Heben Sie dann die Auskommentierung +//des nachstehenden NeutralResourceLanguage-Attributs auf. Aktualisieren Sie "en-US" in der nachstehenden Zeile, +//sodass es mit der UICulture-Einstellung in der Projektdatei übereinstimmt. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //Speicherort der designspezifischen Ressourcenwörterbücher + //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, + // oder in den Anwendungsressourcen-Wörterbüchern nicht gefunden werden kann.) + ResourceDictionaryLocation.SourceAssembly //Speicherort des generischen Ressourcenwörterbuchs + //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, + // designspezifischen Ressourcenwörterbuch nicht gefunden werden kann.) +)] + + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.Designer.cs b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.Designer.cs new file mode 100644 index 000000000..3728126af --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.Designer.cs @@ -0,0 +1,138 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace AA09_DialogBoxes.Properties { + using System; + + + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AA09_DialogBoxes.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Open Dialog ähnelt. + /// + public static string btnOpenDialog { + get { + return ResourceManager.GetString("btnOpenDialog", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Open Question ähnelt. + /// + public static string btnOpenMsg { + get { + return ResourceManager.GetString("btnOpenMsg", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Tutorial #00: Handling of dialogboxes using Comunity-Toolkit ähnelt. + /// + public static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die <UserControl xmlns="https://github.com/avaloniaui" + /// xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + /// xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + /// xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + /// xmlns:vm="clr-namespace:AA09_DialogBoxes.ViewModels" + /// x:Class="AA09_DialogBoxes.Views.DialogView" + /// x:DataType="vm:DialogViewModel" + /// mc:Ignorable="d"> + /// <Design.DataContext> + /// <vm:Di [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + /// + public static string DialogView { + get { + return ResourceManager.GetString("DialogView", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die // *********************************************************************** + ///// Assembly : MVVM_09_DialogBoxes + ///// Author : Mir + ///// Created : 07-21-2022 + ///// + ///// Last Modified By : Mir + ///// Last Modified On : 08-09-2022 + ///// *********************************************************************** + ///// <copyright file="DialogViewModel.cs" company="JC-Soft"> + ///// Copyright © JC-Soft 2021 + ///// </copyright> + ///// <summary></summary> + ///// *************************************************** [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + /// + public static string DialogViewModel { + get { + return ResourceManager.GetString("DialogViewModel", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die MVVM #00 BaseTests ähnelt. + /// + public static string Title { + get { + return ResourceManager.GetString("Title", resourceCulture); + } + } + } +} diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.de.resx b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.de.resx new file mode 100644 index 000000000..acc7312fd --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.de.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Öffne Dialog + + + Meldung ... + + + MVVM #00 Basistests + + \ No newline at end of file diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.en.resx b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.en.resx new file mode 100644 index 000000000..5e0e5c5e9 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.en.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Open Dialog + + + Open Question + + + Tutorial #00: Handling of dialogboxes using Comunity-Toolkit + + \ No newline at end of file diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.resx b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.resx new file mode 100644 index 000000000..373717c2b --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Open Dialog + + + Open Question + + + Tutorial #00: Handling of dialogboxes using Comunity-Toolkit + + + + ..\Views\DialogView.axaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\ViewModels\DialogViewModel.cs;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + MVVM #00 BaseTests + + \ No newline at end of file diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/README.md b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/README.md new file mode 100644 index 000000000..ced54c176 --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/README.md @@ -0,0 +1,77 @@ +# MVVM_09a_CTDialogBoxes + +> Toolkit-friendly dialog pattern. + +## Overview +System.Collections.Hashtable.Detailed + +## Key Learning Goals +- Messenger +- Async dialogs +- Loose coupling + +## Project Structure +- Views: WPF XAML views illustrating bindings and styles +- ViewModels: Reactive presentation logic using either classic or Toolkit paradigms +- Models: Plain data / domain classes kept free of UI concerns +- Services: (Where applicable) abstractions for dialogs, navigation, data access +- Behaviors / Helpers: Reusable interaction patterns extending XAML without code-behind + +## Build & Run +` +dotnet build MVVM_09a_CTDialogBoxes +` +If multiple target frameworks (e.g., net6.0-windows + net481) exist, you can specify one: +` +dotnet build MVVM_09a_CTDialogBoxes -f net6.0-windows +` +Run (where an executable host exists): +` +dotnet run --project MVVM_09a_CTDialogBoxes -f net6.0-windows +` + +## Testing +If a companion test project exists it is listed below. Execute: +` +dotnet test MVVM_09a_CTDialogBoxesTests +` +(If '(none explicit)' the example is illustrated without dedicated automated tests yet.) + +## Test & Coverage Status + +| Metric | Status | +|--------|--------| +| Unit Tests | Implemented (see associated *Tests* project: MVVM_09a_CTDialogBoxesTests) | +| Line Coverage | TBD | +| Branch Coverage | TBD | +| Method Coverage | TBD | +| Complexity Coverage | TBD | + +### Collecting Coverage Locally + +Classic (.NET Framework targets): +` +dotnet test MVVM_09a_CTDialogBoxesTests --framework net481 /p:CollectCoverage=true /p:CoverletOutputFormat=lcov +` + +Modern multi-target (.NET >= 6): +` +dotnet test MVVM_09a_CTDialogBoxesTests -c Debug /p:CollectCoverage=true /p:CoverletOutputFormat=lcov \ + /p:Exclude="[xunit.*]*,[MSTest.*]*,[NUnit.*]*" --logger "trx" --results-directory ./TestResults +` + +To merge coverage from several target frameworks: +` +dotnet tool install --global dotnet-reportgenerator-globaltool +reportgenerator -reports:"**/coverage.info" -targetdir:CoverageReport -reporttypes:HtmlSummary;MarkdownSummaryGithub +` + +> Replace TBD entries above with the real metrics after running the commands. + +## Extending This Example +- Add additional ViewModels to explore more states +- Introduce logging / diagnostics via ILogger abstractions +- Write property-based tests for complex transformations + +## Notes +This README was auto-generated. You can safely edit and commit refinements (the generator will skip existing files unless -Force is used). diff --git a/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Styles/AppDefaultStyles.axaml b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Styles/AppDefaultStyles.axaml new file mode 100644 index 000000000..adc543bdf --- /dev/null +++ b/Avalonia_Apps/AA09_DialogBoxes/AA09_DialogBoxes/Styles/AppDefaultStyles.axaml @@ -0,0 +1,85 @@ + + + + + + + + + + diff --git a/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/DoubleButtonUC.axaml.cs b/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/DoubleButtonUC.axaml.cs new file mode 100644 index 000000000..5331d3c60 --- /dev/null +++ b/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/DoubleButtonUC.axaml.cs @@ -0,0 +1,45 @@ +using Avalonia; +using Avalonia.Controls; +using System.Windows.Input; + +namespace AA16_UserControl1.Views; + +public partial class DoubleButtonUC : UserControl +{ + public DoubleButtonUC() + { + InitializeComponent(); + VisData1 = true; + VisData2 = true; + } + + public static readonly StyledProperty Command1Property = + AvaloniaProperty.Register(nameof(Command1)); + + public static readonly StyledProperty Command2Property = + AvaloniaProperty.Register(nameof(Command2)); + + public ICommand? Command1 + { + get => GetValue(Command1Property); + set => SetValue(Command1Property, value); + } + + public ICommand? Command2 + { + get => GetValue(Command2Property); + set => SetValue(Command2Property, value); + } + + public bool VisData1 { get; set; } + public bool VisData2 { get; set; } + + public string? CommandParameter1 { get; set; } + public string? CommandParameter2 { get; set; } + + public string? Tooltip1 { get; set; } + public string? Tooltip2 { get; set; } + + public string? Image1 { get; set; } + public string? Image2 { get; set; } +} diff --git a/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/LabeldMaxLengthTextbox.axaml b/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/LabeldMaxLengthTextbox.axaml new file mode 100644 index 000000000..278fec985 --- /dev/null +++ b/Avalonia_Apps/AA16_UserControl/AA16_Usercontrol1/Views/LabeldMaxLengthTextbox.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + +