Initial commit: EngineeringSync v1.0.0
Vollständige Implementierung des EngineeringSync-Middleware-Tools: - Windows Service (Kestrel :5050) mit FileSystemWatcher + SignalR - WPF Tray-App mit PendingChanges- und Projektverwaltungs-Fenster - Setup-Wizard (8-Schritte-Installer) - SQLite/EF Core Datenschicht (WAL-Modus) - SHA-256-basiertes Debouncing (2s Fenster) - Backup-System mit konfigurierbarer Aufbewahrung Bugfixes & Verbesserungen: - BUG-1: AppDbContext OnConfiguring invertierte Bedingung behoben - BUG-2: Event-Handler-Leak in TrayApp (Fenster-Singleton-Pattern) - BUG-3: ProjectConfigChanged SignalR-Signal in allen CRUD-Endpoints - BUG-5: Rename-Sync löscht alte Datei im Simulations-Ordner - BUG-6: Doppeltes Dispose von SignalR verhindert - BUG-7: Registry-Deinstallation nur EngineeringSync-Eintrag entfernt - S1: Path-Traversal-Schutz via SafeCombine() im SyncManager - E1: FSW Buffer 64KB + automatischer Re-Scan bei Overflow - E2: Retry-Logik (3x) für gesperrte Dateien mit exponentiellem Backoff - E4: Channel.Writer.TryComplete() beim Shutdown - C2: HubMethodNames-Konstanten statt Magic Strings - E3: Pagination in Changes-API (page/pageSize Query-Parameter) - A1: Fire-and-Forget mit try/catch + Logging Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
17
EngineeringSync.Setup/App.xaml
Normal file
17
EngineeringSync.Setup/App.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<Application x:Class="EngineeringSync.Setup.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="clr-namespace:EngineeringSync.Setup.Converters">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Themes/WizardStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<conv:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
|
||||
<conv:BoolToInverseVisibilityConverter x:Key="BoolToInvVisConverter"/>
|
||||
<conv:LastStepLabelConverter x:Key="LastStepLabelConverter"/>
|
||||
<conv:StringToVisibilityConverter x:Key="StringToVisConverter"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
82
EngineeringSync.Setup/App.xaml.cs
Normal file
82
EngineeringSync.Setup/App.xaml.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.ServiceProcess;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EngineeringSync.Setup;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
if (e.Args.Length > 0 && e.Args[0].Equals("/uninstall", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RunUninstall();
|
||||
Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
var window = new Views.WizardWindow();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void RunUninstall()
|
||||
{
|
||||
var serviceName = "EngineeringSync";
|
||||
try
|
||||
{
|
||||
var sc = new ServiceController(serviceName);
|
||||
if (sc.Status != ServiceControllerStatus.Stopped)
|
||||
{
|
||||
sc.Stop();
|
||||
sc.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc",
|
||||
Arguments = "delete EngineeringSync",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
})?.WaitForExit();
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
using var runKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
|
||||
runKey?.DeleteValue("EngineeringSync.TrayApp", false);
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
Registry.LocalMachine.DeleteSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\EngineeringSync", false);
|
||||
}
|
||||
catch { }
|
||||
|
||||
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
|
||||
var linkPath = Path.Combine(desktop, "EngineeringSync.lnk");
|
||||
if (File.Exists(linkPath))
|
||||
File.Delete(linkPath);
|
||||
|
||||
var startMenu = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||
"Programs", "EngineeringSync");
|
||||
if (Directory.Exists(startMenu))
|
||||
Directory.Delete(startMenu, true);
|
||||
|
||||
var msg = "EngineeringSync wurde erfolgreich deinstalliert.";
|
||||
MessageBox.Show(msg, "Deinstallation", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
10
EngineeringSync.Setup/AssemblyInfo.cs
Normal file
10
EngineeringSync.Setup/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
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)
|
||||
)]
|
||||
BIN
EngineeringSync.Setup/Assets/setup-icon.ico
Normal file
BIN
EngineeringSync.Setup/Assets/setup-icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
40
EngineeringSync.Setup/Converters/WizardConverters.cs
Normal file
40
EngineeringSync.Setup/Converters/WizardConverters.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace EngineeringSync.Setup.Converters;
|
||||
|
||||
public class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object v, Type t, object p, CultureInfo c) =>
|
||||
v is true ? Visibility.Visible : Visibility.Collapsed;
|
||||
public object ConvertBack(object v, Type t, object p, CultureInfo c) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class BoolToInverseVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object v, Type t, object p, CultureInfo c) =>
|
||||
v is true ? Visibility.Collapsed : Visibility.Visible;
|
||||
public object ConvertBack(object v, Type t, object p, CultureInfo c) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gibt "Jetzt installieren" wenn true (letzter normaler Schritt), sonst "Weiter".
|
||||
/// </summary>
|
||||
public class LastStepLabelConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object v, Type t, object p, CultureInfo c) =>
|
||||
v is true ? "Jetzt installieren" : "Weiter";
|
||||
public object ConvertBack(object v, Type t, object p, CultureInfo c) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public class StringToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object v, Type t, object p, CultureInfo c) =>
|
||||
string.IsNullOrEmpty(v as string) ? Visibility.Collapsed : Visibility.Visible;
|
||||
public object ConvertBack(object v, Type t, object p, CultureInfo c) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
23
EngineeringSync.Setup/EngineeringSync.Setup.csproj
Normal file
23
EngineeringSync.Setup/EngineeringSync.Setup.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AssemblyName>EngineeringSync.Setup</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Assets\setup-icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\setup-icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
12
EngineeringSync.Setup/MainWindow.xaml
Normal file
12
EngineeringSync.Setup/MainWindow.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<Window x:Class="EngineeringSync.Setup.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:local="clr-namespace:EngineeringSync.Setup"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
23
EngineeringSync.Setup/MainWindow.xaml.cs
Normal file
23
EngineeringSync.Setup/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace EngineeringSync.Setup;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
304
EngineeringSync.Setup/Services/InstallerService.cs
Normal file
304
EngineeringSync.Setup/Services/InstallerService.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EngineeringSync.Setup.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Führt alle Installationsschritte aus. Gibt Fortschritt und Log-Meldungen
|
||||
/// über Events zurück, damit die UI in Echtzeit aktualisiert werden kann.
|
||||
/// </summary>
|
||||
public class InstallerService(WizardState state)
|
||||
{
|
||||
public event Action<int, string>? Progress;
|
||||
public event Action<string>? LogMessage;
|
||||
|
||||
private void Report(int percent, string step, string? log = null)
|
||||
{
|
||||
Progress?.Invoke(percent, step);
|
||||
LogMessage?.Invoke(log ?? step);
|
||||
}
|
||||
|
||||
/// <summary>Hauptinstallationsablauf – läuft auf einem Background-Thread.</summary>
|
||||
public async Task InstallAsync()
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
// 1. Installationsverzeichnis anlegen
|
||||
Report(5, "Installationsverzeichnis anlegen...");
|
||||
Directory.CreateDirectory(state.InstallPath);
|
||||
await Delay();
|
||||
|
||||
// 2. Programmdateien kopieren
|
||||
Report(15, "Programmdateien werden kopiert...");
|
||||
await CopyApplicationFilesAsync();
|
||||
|
||||
// 3. Datenbank-Verzeichnis anlegen
|
||||
Report(30, "Datenbankverzeichnis anlegen...");
|
||||
var dbDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"EngineeringSync");
|
||||
Directory.CreateDirectory(dbDir);
|
||||
Report(33, "Datenbankverzeichnis angelegt.", $"Datenbank-Pfad: {dbDir}");
|
||||
await Delay();
|
||||
|
||||
// 4. Erstkonfigurations-Datei schreiben
|
||||
Report(40, "Erstkonfiguration wird gespeichert...");
|
||||
WriteFirstRunConfig(dbDir);
|
||||
await Delay();
|
||||
|
||||
// 5. Bestehenden Dienst stoppen & deinstallieren (Upgrade-Szenario)
|
||||
Report(50, "Vorherige Installation wird geprüft...");
|
||||
await StopAndRemoveExistingServiceAsync();
|
||||
await Delay();
|
||||
|
||||
// 6. Windows Service registrieren
|
||||
Report(60, "Windows-Dienst wird registriert...");
|
||||
var serviceExe = Path.Combine(state.InstallPath, "EngineeringSync.Service.exe");
|
||||
await RegisterWindowsServiceAsync(serviceExe);
|
||||
await Delay();
|
||||
|
||||
// 7. Autostart für TrayApp
|
||||
if (state.AutoStartTrayApp)
|
||||
{
|
||||
Report(70, "TrayApp-Autostart wird konfiguriert...");
|
||||
ConfigureTrayAppAutostart();
|
||||
await Delay();
|
||||
}
|
||||
|
||||
// 8. Verknüpfungen erstellen
|
||||
Report(78, "Verknüpfungen werden erstellt...");
|
||||
if (state.CreateDesktopShortcut) CreateShortcut(ShortcutTarget.Desktop);
|
||||
if (state.CreateStartMenuEntry) CreateShortcut(ShortcutTarget.StartMenu);
|
||||
await Delay();
|
||||
|
||||
// 9. Add/Remove Programs Eintrag
|
||||
Report(85, "Deinstallations-Eintrag wird angelegt...");
|
||||
RegisterUninstallEntry();
|
||||
await Delay();
|
||||
|
||||
// 10. Dienst starten
|
||||
if (state.AutoStartService)
|
||||
{
|
||||
Report(92, "Windows-Dienst wird gestartet...");
|
||||
await StartServiceAsync();
|
||||
}
|
||||
|
||||
Report(100, "Installation abgeschlossen.", "✓ EngineeringSync wurde erfolgreich installiert.");
|
||||
});
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────
|
||||
|
||||
private async Task CopyApplicationFilesAsync()
|
||||
{
|
||||
var sourceDir = Path.GetFullPath(AppContext.BaseDirectory).TrimEnd(Path.DirectorySeparatorChar);
|
||||
var targetDir = Path.GetFullPath(state.InstallPath).TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
// Wenn das Setup bereits aus dem Zielverzeichnis läuft (z.B. nach Inno-Setup-Installation),
|
||||
// wurden die Dateien bereits kopiert – nichts zu tun.
|
||||
if (string.Equals(sourceDir, targetDir, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
LogMessage?.Invoke(" Dateien bereits installiert (Inno-Setup) - Kopieren wird übersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
var patterns = new[] { "*.exe", "*.dll", "*.json", "*.pdb" };
|
||||
var allFiles = patterns
|
||||
.SelectMany(p => Directory.GetFiles(sourceDir, p))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < allFiles.Count; i++)
|
||||
{
|
||||
var src = allFiles[i];
|
||||
var dest = Path.Combine(targetDir, Path.GetFileName(src));
|
||||
File.Copy(src, dest, overwrite: true);
|
||||
LogMessage?.Invoke($" Kopiert: {Path.GetFileName(src)}");
|
||||
await Task.Delay(30); // Kurze Pause für UI-Responsivität
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteFirstRunConfig(string dbDir)
|
||||
{
|
||||
var config = new
|
||||
{
|
||||
FirstRun = new
|
||||
{
|
||||
ProjectName = state.ProjectName,
|
||||
EngineeringPath = state.EngineeringPath,
|
||||
SimulationPath = state.SimulationPath,
|
||||
FileExtensions = state.WatchAllFiles ? "*" : state.FileExtensions,
|
||||
WatchAllFiles = state.WatchAllFiles
|
||||
},
|
||||
Backup = new // NEU
|
||||
{
|
||||
BackupEnabled = state.BackupEnabled,
|
||||
BackupPath = state.BackupUseCustomPath ? state.BackupCustomPath : (string?)null,
|
||||
MaxBackupsPerFile = state.MaxBackupsPerFile
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(config,
|
||||
new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
var configPath = Path.Combine(state.InstallPath, "firstrun-config.json");
|
||||
File.WriteAllText(configPath, json);
|
||||
LogMessage?.Invoke($" Konfiguration geschrieben: {configPath}");
|
||||
}
|
||||
|
||||
private async Task StopAndRemoveExistingServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stopResult = await RunProcessAsync("sc", "stop EngineeringSync");
|
||||
if (stopResult.exitCode == 0)
|
||||
{
|
||||
LogMessage?.Invoke(" Bestehender Dienst gestoppt.");
|
||||
await Task.Delay(1500); // Warten bis Dienst wirklich gestoppt
|
||||
}
|
||||
|
||||
await RunProcessAsync("sc", "delete EngineeringSync");
|
||||
LogMessage?.Invoke(" Bestehender Dienst entfernt.");
|
||||
}
|
||||
catch { /* Kein vorheriger Dienst – OK */ }
|
||||
}
|
||||
|
||||
private async Task RegisterWindowsServiceAsync(string exePath)
|
||||
{
|
||||
if (!File.Exists(exePath))
|
||||
{
|
||||
LogMessage?.Invoke($" WARNUNG: Service-EXE nicht gefunden: {exePath}");
|
||||
LogMessage?.Invoke(" (Im Entwicklungsmodus – Service-Registrierung übersprungen)");
|
||||
return;
|
||||
}
|
||||
|
||||
var startType = state.AutoStartService ? "auto" : "demand";
|
||||
var args = $"create EngineeringSync binPath= \"{exePath}\" " +
|
||||
$"start= {startType} " +
|
||||
$"DisplayName= \"EngineeringSync Watcher Service\"";
|
||||
|
||||
var (exitCode, output, error) = await RunProcessAsync("sc", args);
|
||||
|
||||
if (exitCode == 0)
|
||||
LogMessage?.Invoke(" ✓ Windows-Dienst registriert.");
|
||||
else
|
||||
{
|
||||
LogMessage?.Invoke($" FEHLER bei sc create (Code {exitCode}): {error}");
|
||||
throw new InvalidOperationException($"Service-Registrierung fehlgeschlagen: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureTrayAppAutostart()
|
||||
{
|
||||
var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe");
|
||||
if (!File.Exists(trayExe))
|
||||
{
|
||||
LogMessage?.Invoke(" TrayApp nicht gefunden – Autostart übersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
using var key = Registry.CurrentUser.OpenSubKey(
|
||||
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", writable: true)!;
|
||||
key.SetValue("EngineeringSync.TrayApp", $"\"{trayExe}\"");
|
||||
LogMessage?.Invoke(" ✓ TrayApp-Autostart in Registry eingetragen.");
|
||||
}
|
||||
|
||||
private enum ShortcutTarget { Desktop, StartMenu }
|
||||
|
||||
private void CreateShortcut(ShortcutTarget target)
|
||||
{
|
||||
var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe");
|
||||
|
||||
var dir = target == ShortcutTarget.Desktop
|
||||
? Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory)
|
||||
: Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||
"Programs", "EngineeringSync");
|
||||
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var linkPath = Path.Combine(dir, "EngineeringSync.lnk");
|
||||
CreateWindowsShortcut(linkPath, trayExe, state.InstallPath, "EngineeringSync Tray App");
|
||||
|
||||
LogMessage?.Invoke($" ✓ Verknüpfung erstellt: {linkPath}");
|
||||
}
|
||||
|
||||
private static void CreateWindowsShortcut(string linkPath, string targetPath,
|
||||
string workingDir, string description)
|
||||
{
|
||||
// WScript.Shell COM-Interop für Verknüpfungs-Erstellung
|
||||
var type = Type.GetTypeFromProgID("WScript.Shell")!;
|
||||
dynamic shell = Activator.CreateInstance(type)!;
|
||||
var shortcut = shell.CreateShortcut(linkPath);
|
||||
shortcut.TargetPath = targetPath;
|
||||
shortcut.WorkingDirectory = workingDir;
|
||||
shortcut.Description = description;
|
||||
shortcut.Save();
|
||||
Marshal.FinalReleaseComObject(shortcut);
|
||||
Marshal.FinalReleaseComObject(shell);
|
||||
}
|
||||
|
||||
private void RegisterUninstallEntry()
|
||||
{
|
||||
using var key = Registry.LocalMachine.CreateSubKey(
|
||||
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\EngineeringSync");
|
||||
|
||||
var uninstallExe = Path.Combine(state.InstallPath, "EngineeringSync.Setup.exe");
|
||||
key.SetValue("DisplayName", "EngineeringSync");
|
||||
key.SetValue("DisplayVersion", "1.0.0");
|
||||
key.SetValue("Publisher", "EngineeringSync");
|
||||
key.SetValue("InstallLocation", state.InstallPath);
|
||||
key.SetValue("UninstallString", $"\"{uninstallExe}\" /uninstall");
|
||||
key.SetValue("NoModify", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("NoRepair", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("EstimatedSize", 45000, RegistryValueKind.DWord);
|
||||
key.SetValue("DisplayIcon", Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe"));
|
||||
|
||||
LogMessage?.Invoke(" ✓ Deinstallations-Eintrag in Registry angelegt.");
|
||||
}
|
||||
|
||||
private async Task StartServiceAsync()
|
||||
{
|
||||
var (exitCode, _, error) = await RunProcessAsync("sc", "start EngineeringSync");
|
||||
if (exitCode == 0)
|
||||
LogMessage?.Invoke(" ✓ Windows-Dienst gestartet.");
|
||||
else
|
||||
LogMessage?.Invoke($" WARNUNG: Dienst konnte nicht gestartet werden ({error})");
|
||||
}
|
||||
|
||||
public void LaunchTrayApp()
|
||||
{
|
||||
var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe");
|
||||
if (File.Exists(trayExe))
|
||||
Process.Start(new ProcessStartInfo(trayExe) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private static async Task<(int exitCode, string output, string error)> RunProcessAsync(
|
||||
string fileName, string arguments)
|
||||
{
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
var error = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
return (process.ExitCode, output.Trim(), error.Trim());
|
||||
}
|
||||
|
||||
private static Task Delay() => Task.Delay(400);
|
||||
}
|
||||
361
EngineeringSync.Setup/Themes/WizardStyles.xaml
Normal file
361
EngineeringSync.Setup/Themes/WizardStyles.xaml
Normal file
@@ -0,0 +1,361 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
FARBEN & BRUSHES
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<Color x:Key="AccentColor">#0078D4</Color>
|
||||
<Color x:Key="AccentHoverColor">#106EBE</Color>
|
||||
<Color x:Key="AccentPressColor">#005A9E</Color>
|
||||
<Color x:Key="SidebarTopColor">#1B1B2F</Color>
|
||||
<Color x:Key="SidebarBottomColor">#16213E</Color>
|
||||
<Color x:Key="SuccessColor">#107C10</Color>
|
||||
<Color x:Key="ErrorColor">#C42B1C</Color>
|
||||
<Color x:Key="WarningColor">#CA5010</Color>
|
||||
<Color x:Key="SurfaceColor">#FAFAFA</Color>
|
||||
<Color x:Key="BorderColor">#E0E0E0</Color>
|
||||
<Color x:Key="TextPrimaryColor">#1A1A1A</Color>
|
||||
<Color x:Key="TextSecondaryColor">#5F5F5F</Color>
|
||||
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||
<SolidColorBrush x:Key="AccentHoverBrush" Color="{StaticResource AccentHoverColor}"/>
|
||||
<SolidColorBrush x:Key="AccentPressBrush" Color="{StaticResource AccentPressColor}"/>
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
|
||||
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource ErrorColor}"/>
|
||||
<SolidColorBrush x:Key="WarningBrush" Color="{StaticResource WarningColor}"/>
|
||||
<SolidColorBrush x:Key="SurfaceBrush" Color="{StaticResource SurfaceColor}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimaryColor}"/>
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondaryColor}"/>
|
||||
|
||||
<LinearGradientBrush x:Key="SidebarGradient" StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Color="{StaticResource SidebarTopColor}" Offset="0"/>
|
||||
<GradientStop Color="{StaticResource SidebarBottomColor}" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
TYPOGRAPHY
|
||||
═══════════════════════════════════════════ -->
|
||||
<FontFamily x:Key="UiFont">Segoe UI</FontFamily>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
BUTTON STYLES
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- Primärer Akzent-Button -->
|
||||
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="24,10"/>
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background"
|
||||
Value="{StaticResource AccentHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Background"
|
||||
Value="{StaticResource AccentPressBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Background" Value="#BDBDBD"/>
|
||||
<Setter Property="Foreground" Value="#888888"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Sekundärer Button (Ghost) -->
|
||||
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="20,10"/>
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="#F5F5F5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="#EBEBEB"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Foreground" Value="#BDBDBD"/>
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="#E0E0E0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Icon-Button für Folder-Browse -->
|
||||
<Style x:Key="IconButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Width" Value="36"/>
|
||||
<Setter Property="Height" Value="36"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="bd"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bd" Property="Background" Value="#F0F7FF"/>
|
||||
<Setter TargetName="bd" Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
TEXTBOX STYLE
|
||||
═══════════════════════════════════════════ -->
|
||||
<Style x:Key="ModernTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="2,0"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter TargetName="border" Property="BorderBrush"
|
||||
Value="{StaticResource AccentBrush}"/>
|
||||
<Setter TargetName="border" Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="Foreground" Value="#BDBDBD"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
CHECKBOX STYLE
|
||||
═══════════════════════════════════════════ -->
|
||||
<Style x:Key="ModernCheckBoxStyle" TargetType="CheckBox">
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border x:Name="box"
|
||||
Width="18" Height="18"
|
||||
CornerRadius="4"
|
||||
BorderThickness="2"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
Background="White"
|
||||
Margin="0,0,8,0">
|
||||
<TextBlock x:Name="check"
|
||||
Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="11"
|
||||
Foreground="White"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="Collapsed"/>
|
||||
</Border>
|
||||
<ContentPresenter VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="box" Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter TargetName="box" Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter TargetName="check" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="box" Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
LABELS & TYPOGRAPHY
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<Style x:Key="PageTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="24"/>
|
||||
<Setter Property="FontWeight" Value="Light"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
|
||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PageSubtitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="Margin" Value="0,0,0,24"/>
|
||||
<Setter Property="LineHeight" Value="20"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="FieldLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/>
|
||||
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
SIDEBAR STEP ITEM
|
||||
═══════════════════════════════════════════ -->
|
||||
<Style x:Key="StepItemStyle" TargetType="ContentControl">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="36"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Kreis-Indikator -->
|
||||
<Border x:Name="circle"
|
||||
Width="28" Height="28"
|
||||
CornerRadius="14"
|
||||
Background="#2A3050"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock x:Name="iconText"
|
||||
Text="{Binding Icon}"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
Foreground="#8899BB"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Titel -->
|
||||
<TextBlock x:Name="titleText"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Title}"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="12"
|
||||
Foreground="#8899BB"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,0,0"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<!-- Aktiver Schritt -->
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter TargetName="circle" Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter TargetName="iconText" Property="Foreground" Value="White"/>
|
||||
<Setter TargetName="titleText" Property="Foreground" Value="White"/>
|
||||
<Setter TargetName="titleText" Property="FontWeight" Value="SemiBold"/>
|
||||
</DataTrigger>
|
||||
<!-- Abgeschlossener Schritt -->
|
||||
<DataTrigger Binding="{Binding IsCompleted}" Value="True">
|
||||
<Setter TargetName="circle" Property="Background" Value="#107C10"/>
|
||||
<Setter TargetName="iconText" Property="Text" Value=""/>
|
||||
<Setter TargetName="iconText" Property="Foreground" Value="White"/>
|
||||
<Setter TargetName="titleText" Property="Foreground" Value="#88CCAA"/>
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
INFO CARD (für Zusammenfassung etc.)
|
||||
═══════════════════════════════════════════ -->
|
||||
<Style x:Key="InfoCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="16"/>
|
||||
<Setter Property="Margin" Value="0,0,0,12"/>
|
||||
</Style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
PROGRESS BAR
|
||||
═══════════════════════════════════════════ -->
|
||||
<Style x:Key="ModernProgressBarStyle" TargetType="ProgressBar">
|
||||
<Setter Property="Height" Value="6"/>
|
||||
<Setter Property="Background" Value="#E0E0E0"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ProgressBar">
|
||||
<Grid>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="3"/>
|
||||
<Border x:Name="PART_Track" CornerRadius="3" ClipToBounds="True">
|
||||
<Border x:Name="PART_Indicator"
|
||||
Background="{TemplateBinding Foreground}"
|
||||
CornerRadius="3"
|
||||
HorizontalAlignment="Left"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
36
EngineeringSync.Setup/ViewModels/WizardState.cs
Normal file
36
EngineeringSync.Setup/ViewModels/WizardState.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.IO;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace EngineeringSync.Setup.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Zentrales Datenmodell das durch alle Wizard-Schritte weitergereicht wird.
|
||||
/// </summary>
|
||||
public partial class WizardState : ObservableObject
|
||||
{
|
||||
// --- Installationspfad ---
|
||||
[ObservableProperty]
|
||||
private string _installPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
||||
"EngineeringSync");
|
||||
|
||||
// --- Erstes Projekt ---
|
||||
[ObservableProperty] private string _projectName = "Mein Projekt";
|
||||
[ObservableProperty] private string _engineeringPath = string.Empty;
|
||||
[ObservableProperty] private string _simulationPath = string.Empty;
|
||||
[ObservableProperty] private bool _watchAllFiles = false;
|
||||
[ObservableProperty] private string _fileExtensions = ".jt,.cojt,.xml";
|
||||
|
||||
// --- Service-Optionen ---
|
||||
[ObservableProperty] private bool _autoStartService = true;
|
||||
[ObservableProperty] private bool _autoStartTrayApp = true;
|
||||
[ObservableProperty] private bool _startAfterInstall = true;
|
||||
[ObservableProperty] private bool _createDesktopShortcut = true;
|
||||
[ObservableProperty] private bool _createStartMenuEntry = true;
|
||||
|
||||
// --- Backup-Einstellungen ---
|
||||
[ObservableProperty] private bool _backupEnabled = true;
|
||||
[ObservableProperty] private bool _backupUseCustomPath = false;
|
||||
[ObservableProperty] private string _backupCustomPath = string.Empty;
|
||||
[ObservableProperty] private int _maxBackupsPerFile = 0;
|
||||
}
|
||||
135
EngineeringSync.Setup/ViewModels/WizardViewModel.cs
Normal file
135
EngineeringSync.Setup/ViewModels/WizardViewModel.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using EngineeringSync.Setup.Services;
|
||||
using EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
namespace EngineeringSync.Setup.ViewModels;
|
||||
|
||||
public partial class WizardViewModel : ObservableObject
|
||||
{
|
||||
public WizardState State { get; } = new();
|
||||
|
||||
[ObservableProperty] private WizardPageBase _currentPage = null!;
|
||||
[ObservableProperty] private int _currentStepIndex;
|
||||
[ObservableProperty] private bool _canGoBack;
|
||||
[ObservableProperty] private bool _canGoNext = true;
|
||||
[ObservableProperty] private bool _isLastStep;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowNextButton))]
|
||||
private bool _isInstalling;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowNextButton))]
|
||||
private bool _isCompleted;
|
||||
|
||||
public bool ShowNextButton => !IsCompleted && !IsInstalling;
|
||||
|
||||
public ObservableCollection<WizardStep> Steps { get; } =
|
||||
[
|
||||
new("Willkommen", "\uE80F"), // Home icon
|
||||
new("Installation", "\uE7B7"), // Folder icon
|
||||
new("Erstes Projekt","\uE8B7"), // Link icon
|
||||
new("Backup", "\uE72E"), // Save icon (NEU)
|
||||
new("Optionen", "\uE713"), // Settings icon
|
||||
new("Zusammenfassung","\uE8A9"), // List icon
|
||||
new("Installation", "\uE896"), // Download icon
|
||||
new("Fertig", "\uE930"), // Completed icon
|
||||
];
|
||||
|
||||
private readonly List<Func<WizardPageBase>> _pageFactories;
|
||||
private readonly InstallerService _installer;
|
||||
|
||||
public WizardViewModel()
|
||||
{
|
||||
_installer = new InstallerService(State);
|
||||
|
||||
_pageFactories =
|
||||
[
|
||||
() => new WelcomePage(this),
|
||||
() => new InstallPathPage(this),
|
||||
() => new FirstProjectPage(this),
|
||||
() => new BackupOptionsPage(this), // NEU – Index 3
|
||||
() => new ServiceOptionsPage(this),
|
||||
() => new SummaryPage(this),
|
||||
() => new InstallingPage(this, _installer),
|
||||
() => new CompletionPage(this),
|
||||
];
|
||||
|
||||
NavigateTo(0);
|
||||
}
|
||||
|
||||
public void NavigateTo(int index)
|
||||
{
|
||||
CurrentStepIndex = index;
|
||||
CurrentPage = _pageFactories[index]();
|
||||
Steps[index].IsActive = true;
|
||||
|
||||
for (int i = 0; i < Steps.Count; i++)
|
||||
{
|
||||
Steps[i].IsCompleted = i < index;
|
||||
Steps[i].IsActive = i == index;
|
||||
}
|
||||
|
||||
CanGoBack = index > 0 && index < Steps.Count - 1 && !IsInstalling;
|
||||
IsLastStep = index == Steps.Count - 2; // "Zusammenfassung" ist letzter normaler Schritt
|
||||
// IsInstalling wird NICHT hier gesetzt – wird von InstallingPage kontrolliert
|
||||
IsCompleted = index == Steps.Count - 1;
|
||||
CanGoNext = !IsInstalling && !IsCompleted;
|
||||
}
|
||||
|
||||
public void SetInstallingState(bool installing)
|
||||
{
|
||||
IsInstalling = installing;
|
||||
// CanGoNext wird durch die Bedingung !IsInstalling && !IsCompleted neu evaluiert
|
||||
CanGoNext = !IsInstalling && !IsCompleted;
|
||||
// GoBackCommand und CancelCommand CanExecute Bedingungen neu evaluieren
|
||||
GoBackCommand.NotifyCanExecuteChanged();
|
||||
CancelCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanExecuteGoBack))]
|
||||
private void GoBack() => NavigateTo(CurrentStepIndex - 1);
|
||||
|
||||
private bool CanExecuteGoBack => CanGoBack && !IsInstalling;
|
||||
|
||||
[RelayCommand]
|
||||
private void GoNext()
|
||||
{
|
||||
if (!CurrentPage.Validate()) return;
|
||||
if (CurrentStepIndex < _pageFactories.Count - 1)
|
||||
NavigateTo(CurrentStepIndex + 1);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanExecuteCancel))]
|
||||
private void Cancel()
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
var result = MessageBox.Show(
|
||||
"Setup wirklich abbrechen?",
|
||||
"EngineeringSync Setup",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private bool CanExecuteCancel => !IsInstalling && !IsCompleted;
|
||||
|
||||
[RelayCommand]
|
||||
private void Finish()
|
||||
{
|
||||
if (State.StartAfterInstall)
|
||||
_installer.LaunchTrayApp();
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class WizardStep(string title, string icon) : ObservableObject
|
||||
{
|
||||
public string Title { get; } = title;
|
||||
public string Icon { get; } = icon;
|
||||
[ObservableProperty] private bool _isActive;
|
||||
[ObservableProperty] private bool _isCompleted;
|
||||
}
|
||||
86
EngineeringSync.Setup/Views/Pages/BackupOptionsPage.xaml
Normal file
86
EngineeringSync.Setup/Views/Pages/BackupOptionsPage.xaml
Normal file
@@ -0,0 +1,86 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.BackupOptionsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Backup-Einstellungen"
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Legen Sie fest, ob und wie Sicherungskopien vor dem Überschreiben von Simulationsdateien erstellt werden."/>
|
||||
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Master-Toggle -->
|
||||
<local:OptionCard Icon="" Title="Backups aktivieren"
|
||||
Description="Vor jeder Dateiüberschreibung wird automatisch eine .bak-Sicherungskopie erstellt"
|
||||
IsChecked="{Binding BackupEnabled, Mode=TwoWay}"/>
|
||||
|
||||
<!-- Optionen (nur wenn Backup aktiviert) -->
|
||||
<StackPanel Visibility="{Binding BackupEnabled,
|
||||
Converter={StaticResource BoolToVisConverter}}">
|
||||
|
||||
<!-- Speicherort -->
|
||||
<TextBlock Text="SPEICHERORT" Style="{StaticResource FieldLabelStyle}"
|
||||
Margin="0,16,0,8"/>
|
||||
|
||||
<!-- Mode=OneWay: gegenseitiger Ausschluss läuft über GroupName + TwoWay am zweiten Radio -->
|
||||
<RadioButton GroupName="BackupLocation"
|
||||
Content="Gleicher Ordner wie die Simulationsdatei"
|
||||
IsChecked="{Binding BackupUseCustomPath,
|
||||
Converter={StaticResource BoolToInvVisConverter},
|
||||
Mode=OneWay}"
|
||||
FontFamily="Segoe UI" FontSize="13" Margin="0,0,0,8"/>
|
||||
|
||||
<RadioButton GroupName="BackupLocation"
|
||||
Content="Eigener Backup-Ordner"
|
||||
IsChecked="{Binding BackupUseCustomPath, Mode=TwoWay}"
|
||||
FontFamily="Segoe UI" FontSize="13" Margin="0,0,0,8"/>
|
||||
|
||||
<!-- Pfad-Eingabe (nur bei eigenem Ordner) -->
|
||||
<DockPanel Margin="0,0,0,16"
|
||||
Visibility="{Binding BackupUseCustomPath,
|
||||
Converter={StaticResource BoolToVisConverter}}">
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButtonStyle}"
|
||||
Margin="6,0,0,0" Click="BrowseBackupPath_Click"
|
||||
ToolTip="Backup-Verzeichnis wählen">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14" Foreground="#0078D4"/>
|
||||
</Button>
|
||||
<TextBox Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding BackupCustomPath, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Aufbewahrung -->
|
||||
<TextBlock Text="AUFBEWAHRUNG" Style="{StaticResource FieldLabelStyle}"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
|
||||
<TextBlock Text="Maximal" FontFamily="Segoe UI" FontSize="13"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<TextBox x:Name="MaxBackupsBox"
|
||||
Width="60" Height="32" Padding="6,0"
|
||||
FontFamily="Segoe UI" FontSize="13"
|
||||
Text="{Binding MaxBackupsPerFile, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center"/>
|
||||
<TextBlock Text="Backups pro Datei" FontFamily="Segoe UI" FontSize="13"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="(0 = unbegrenzt, alle Backups behalten)"
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F"
|
||||
Margin="0,2,0,0"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
39
EngineeringSync.Setup/Views/Pages/BackupOptionsPage.xaml.cs
Normal file
39
EngineeringSync.Setup/Views/Pages/BackupOptionsPage.xaml.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Windows;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class BackupOptionsPage : WizardPageBase
|
||||
{
|
||||
public BackupOptionsPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BrowseBackupPath_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new OpenFolderDialog
|
||||
{
|
||||
Title = "Backup-Verzeichnis wählen",
|
||||
InitialDirectory = string.IsNullOrEmpty(Wizard.State.BackupCustomPath)
|
||||
? null
|
||||
: Wizard.State.BackupCustomPath
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
Wizard.State.BackupCustomPath = dlg.FolderName;
|
||||
}
|
||||
|
||||
public override bool Validate()
|
||||
{
|
||||
if (Wizard.State.BackupEnabled &&
|
||||
Wizard.State.BackupUseCustomPath &&
|
||||
string.IsNullOrWhiteSpace(Wizard.State.BackupCustomPath))
|
||||
{
|
||||
MessageBox.Show("Bitte wählen Sie einen Backup-Ordner oder deaktivieren Sie die Option 'Eigener Backup-Ordner'.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
62
EngineeringSync.Setup/Views/Pages/CompletionPage.xaml
Normal file
62
EngineeringSync.Setup/Views/Pages/CompletionPage.xaml
Normal file
@@ -0,0 +1,62 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.CompletionPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid>
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
MaxWidth="400" Margin="40">
|
||||
|
||||
<!-- Großes Erfolgs-Icon -->
|
||||
<Border Width="80" Height="80" CornerRadius="40"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,24">
|
||||
<Border.Background>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Color="#E8F5E9" Offset="0"/>
|
||||
<GradientStop Color="#C8E6C9" Offset="1"/>
|
||||
</RadialGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="40"
|
||||
Foreground="#107C10" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Installation erfolgreich!"
|
||||
FontFamily="Segoe UI" FontSize="22" FontWeight="Light"
|
||||
Foreground="#1A1A1A" HorizontalAlignment="Center"
|
||||
Margin="0,0,0,12"/>
|
||||
|
||||
<TextBlock FontFamily="Segoe UI" FontSize="13" Foreground="#5F5F5F"
|
||||
TextAlignment="Center" TextWrapping="Wrap" LineHeight="22"
|
||||
Margin="0,0,0,32">
|
||||
<Run Text="EngineeringSync wurde erfolgreich installiert und konfiguriert."/>
|
||||
<LineBreak/>
|
||||
<Run Text="Der Windows-Dienst läuft im Hintergrund und überwacht Ihren Engineering-Ordner."/>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Status-Chips -->
|
||||
<WrapPanel HorizontalAlignment="Center" Margin="0,0,0,32">
|
||||
<Border Background="#E8F5E9" CornerRadius="12" Padding="12,6" Margin="4">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="11" Foreground="#107C10" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Dienst aktiv" FontFamily="Segoe UI"
|
||||
FontSize="12" Foreground="#107C10"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="#E3F2FD" CornerRadius="12" Padding="12,6" Margin="4">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="11" Foreground="#0078D4" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Tray-App startet" FontFamily="Segoe UI"
|
||||
FontSize="12" Foreground="#0078D4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<TextBlock FontFamily="Segoe UI" FontSize="12" Foreground="#888888"
|
||||
TextAlignment="Center" TextWrapping="Wrap"
|
||||
Text="Klicken Sie auf "Schließen" um den Assistenten zu beenden."/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
11
EngineeringSync.Setup/Views/Pages/CompletionPage.xaml.cs
Normal file
11
EngineeringSync.Setup/Views/Pages/CompletionPage.xaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class CompletionPage : WizardPageBase
|
||||
{
|
||||
public CompletionPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
17
EngineeringSync.Setup/Views/Pages/FeatureRow.xaml
Normal file
17
EngineeringSync.Setup/Views/Pages/FeatureRow.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<UserControl x:Class="EngineeringSync.Setup.Views.Pages.FeatureRow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Margin="0,0,0,12">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Width="36" Height="36" CornerRadius="8" Background="#F0F7FF" Margin="0,0,12,0">
|
||||
<TextBlock x:Name="IconText" FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
Foreground="#0078D4" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TitleText" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A"/>
|
||||
<TextBlock x:Name="DescText" FontFamily="Segoe UI" FontSize="11"
|
||||
Foreground="#5F5F5F" TextWrapping="Wrap" MaxWidth="420"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
25
EngineeringSync.Setup/Views/Pages/FeatureRow.xaml.cs
Normal file
25
EngineeringSync.Setup/Views/Pages/FeatureRow.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class FeatureRow : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty IconProperty =
|
||||
DependencyProperty.Register(nameof(Icon), typeof(string), typeof(FeatureRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((FeatureRow)d).IconText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(FeatureRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((FeatureRow)d).TitleText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty =
|
||||
DependencyProperty.Register(nameof(Description), typeof(string), typeof(FeatureRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((FeatureRow)d).DescText.Text = (string)e.NewValue));
|
||||
|
||||
public string Icon { get => (string)GetValue(IconProperty); set => SetValue(IconProperty, value); }
|
||||
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public string Description { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); }
|
||||
|
||||
public FeatureRow() => InitializeComponent();
|
||||
}
|
||||
138
EngineeringSync.Setup/Views/Pages/FirstProjectPage.xaml
Normal file
138
EngineeringSync.Setup/Views/Pages/FirstProjectPage.xaml
Normal file
@@ -0,0 +1,138 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.FirstProjectPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Erstes Projekt konfigurieren"
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Definieren Sie Ihr erstes überwachtes Projekt. Sie können später über die Tray-App beliebig viele weitere Projekte hinzufügen."/>
|
||||
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Projektname -->
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<TextBlock Text="PROJEKTNAME" Style="{StaticResource FieldLabelStyle}"/>
|
||||
<TextBox Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding ProjectName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Engineering-Pfad -->
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<TextBlock Style="{StaticResource FieldLabelStyle}">
|
||||
<Run Text="ENGINEERING-QUELLPFAD "/>
|
||||
<Run Text="(wird überwacht)" Foreground="#0078D4" FontWeight="Normal"/>
|
||||
</TextBlock>
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButtonStyle}"
|
||||
Margin="6,0,0,0" Click="BrowseEngineering_Click"
|
||||
ToolTip="Engineering-Verzeichnis wählen">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14" Foreground="#0078D4"/>
|
||||
</Button>
|
||||
<TextBox Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding EngineeringPath, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
</DockPanel>
|
||||
<TextBlock Text="Änderungen in diesem Verzeichnis werden protokolliert und dem Simulations-Ingenieur gemeldet."
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Simulations-Pfad -->
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<TextBlock Style="{StaticResource FieldLabelStyle}">
|
||||
<Run Text="SIMULATIONS-ZIELPFAD "/>
|
||||
<Run Text="(Sync-Ziel)" Foreground="#107C10" FontWeight="Normal"/>
|
||||
</TextBlock>
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButtonStyle}"
|
||||
Margin="6,0,0,0" Click="BrowseSimulation_Click"
|
||||
ToolTip="Simulations-Verzeichnis wählen">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14" Foreground="#107C10"/>
|
||||
</Button>
|
||||
<TextBox Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding SimulationPath, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
</DockPanel>
|
||||
<TextBlock Text="Freigegebene Änderungen werden kontrolliert in dieses Verzeichnis kopiert (mit automatischem Backup)."
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Dateiüberwachung -->
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<TextBlock Text="ÜBERWACHTE DATEIEN" Style="{StaticResource FieldLabelStyle}"/>
|
||||
|
||||
<!-- Option: Alle Dateien -->
|
||||
<Border Background="#F0F8FF" CornerRadius="6" Padding="12,10" Margin="0,4,0,8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox x:Name="WatchAllCheck"
|
||||
IsChecked="{Binding WatchAllFiles}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Alle Dateitypen überwachen"
|
||||
FontFamily="Segoe UI" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="#1B1B2F"/>
|
||||
<TextBlock Text="Jede Dateiänderung im Ordner wird protokolliert (unabhängig von der Endung)."
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Spezifische Endungen (nur wenn nicht "Alle Dateien") -->
|
||||
<StackPanel Visibility="{Binding WatchAllFiles,
|
||||
Converter={StaticResource BoolToInvVisConverter}}">
|
||||
<TextBlock Text="DATEIENDUNGEN (komma-getrennt)"
|
||||
Style="{StaticResource FieldLabelStyle}" Margin="0,0,0,6"/>
|
||||
|
||||
<!-- Quick-Select Buttons -->
|
||||
<WrapPanel Margin="0,0,0,8">
|
||||
<Button Content=".jt" Tag=".jt" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".cojt" Tag=".cojt" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".xml" Tag=".xml" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".stp" Tag=".stp" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".step" Tag=".step" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".igs" Tag=".igs" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".iges" Tag=".iges" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
<Button Content=".prt" Tag=".prt" Click="AddExt_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Padding="8,4" Margin="0,0,6,6" Height="26" FontSize="11"/>
|
||||
</WrapPanel>
|
||||
|
||||
<TextBox x:Name="ExtensionsBox"
|
||||
Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding FileExtensions, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
<TextBlock Text="Klicke auf einen Typ um ihn hinzuzufügen, oder tippe direkt."
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
79
EngineeringSync.Setup/Views/Pages/FirstProjectPage.xaml.cs
Normal file
79
EngineeringSync.Setup/Views/Pages/FirstProjectPage.xaml.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class FirstProjectPage : WizardPageBase
|
||||
{
|
||||
public FirstProjectPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BrowseEngineering_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new OpenFolderDialog
|
||||
{
|
||||
Title = "Engineering-Quellpfad wählen",
|
||||
InitialDirectory = Wizard.State.EngineeringPath
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
Wizard.State.EngineeringPath = dlg.FolderName;
|
||||
}
|
||||
|
||||
private void BrowseSimulation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new OpenFolderDialog
|
||||
{
|
||||
Title = "Simulations-Zielpfad wählen",
|
||||
InitialDirectory = Wizard.State.SimulationPath
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
Wizard.State.SimulationPath = dlg.FolderName;
|
||||
}
|
||||
|
||||
private void AddExt_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var ext = (sender as Button)?.Tag?.ToString();
|
||||
if (string.IsNullOrEmpty(ext)) return;
|
||||
|
||||
var current = Wizard.State.FileExtensions?.Trim() ?? string.Empty;
|
||||
var parts = current.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (!parts.Contains(ext, StringComparer.OrdinalIgnoreCase))
|
||||
Wizard.State.FileExtensions = current.Length > 0 ? current + "," + ext : ext;
|
||||
}
|
||||
|
||||
public override bool Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Wizard.State.ProjectName))
|
||||
{
|
||||
MessageBox.Show("Bitte geben Sie einen Projektnamen an.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Wizard.State.EngineeringPath) ||
|
||||
!Directory.Exists(Wizard.State.EngineeringPath))
|
||||
{
|
||||
MessageBox.Show("Der Engineering-Pfad existiert nicht oder ist nicht angegeben.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Wizard.State.SimulationPath) ||
|
||||
!Directory.Exists(Wizard.State.SimulationPath))
|
||||
{
|
||||
MessageBox.Show("Der Simulations-Pfad existiert nicht oder ist nicht angegeben.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
if (!Wizard.State.WatchAllFiles && string.IsNullOrWhiteSpace(Wizard.State.FileExtensions))
|
||||
{
|
||||
MessageBox.Show("Bitte geben Sie mindestens eine Dateiendung an oder wählen Sie 'Alle Dateitypen überwachen'.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
10
EngineeringSync.Setup/Views/Pages/InstallItem.xaml
Normal file
10
EngineeringSync.Setup/Views/Pages/InstallItem.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<UserControl x:Class="EngineeringSync.Setup.Views.Pages.InstallItem"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Margin="0,2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="10"
|
||||
Foreground="#107C10" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="ItemText" FontFamily="Consolas" FontSize="12" Foreground="#1A1A1A"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
14
EngineeringSync.Setup/Views/Pages/InstallItem.xaml.cs
Normal file
14
EngineeringSync.Setup/Views/Pages/InstallItem.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class InstallItem : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register(nameof(Text), typeof(string), typeof(InstallItem),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((InstallItem)d).ItemText.Text = (string)e.NewValue));
|
||||
|
||||
public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
|
||||
public InstallItem() => InitializeComponent();
|
||||
}
|
||||
61
EngineeringSync.Setup/Views/Pages/InstallPathPage.xaml
Normal file
61
EngineeringSync.Setup/Views/Pages/InstallPathPage.xaml
Normal file
@@ -0,0 +1,61 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.InstallPathPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Installationsverzeichnis"
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Wählen Sie das Verzeichnis, in dem EngineeringSync installiert werden soll. Der Windows-Dienst und die Tray-App werden dort abgelegt."/>
|
||||
|
||||
<!-- Pfad-Auswahl -->
|
||||
<StackPanel Grid.Row="2" Margin="0,0,0,20">
|
||||
<TextBlock Text="INSTALLATIONSPFAD" Style="{StaticResource FieldLabelStyle}"/>
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButtonStyle}"
|
||||
Margin="6,0,0,0" ToolTip="Verzeichnis auswählen"
|
||||
Click="Browse_Click">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14" Foreground="#0078D4"/>
|
||||
</Button>
|
||||
<TextBox x:Name="PathBox"
|
||||
Style="{StaticResource ModernTextBoxStyle}"
|
||||
Text="{Binding InstallPath, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="36"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Speicherplatz-Hinweis -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="11" Foreground="#5F5F5F" Margin="0,0,6,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Benötigter Speicherplatz: ca. 45 MB"
|
||||
FontFamily="Segoe UI" FontSize="11" Foreground="#5F5F5F"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Info-Karte -->
|
||||
<Border Grid.Row="3" Style="{StaticResource InfoCardStyle}" VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14" Foreground="#0078D4" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="Folgende Komponenten werden installiert:"
|
||||
FontFamily="Segoe UI" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="#1A1A1A"/>
|
||||
</StackPanel>
|
||||
<local:InstallItem Text="EngineeringSync.Service.exe (Windows-Dienst)"/>
|
||||
<local:InstallItem Text="EngineeringSync.TrayApp.exe (Tray-Anwendung)"/>
|
||||
<local:InstallItem Text="engineeringsync.db (Datenbank)"/>
|
||||
<local:InstallItem Text="appsettings.json (Konfiguration)"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
35
EngineeringSync.Setup/Views/Pages/InstallPathPage.xaml.cs
Normal file
35
EngineeringSync.Setup/Views/Pages/InstallPathPage.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Windows;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class InstallPathPage : WizardPageBase
|
||||
{
|
||||
public InstallPathPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Browse_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new OpenFolderDialog
|
||||
{
|
||||
Title = "Installationsverzeichnis auswählen",
|
||||
InitialDirectory = Wizard.State.InstallPath
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
Wizard.State.InstallPath = dlg.FolderName;
|
||||
}
|
||||
|
||||
public override bool Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Wizard.State.InstallPath))
|
||||
{
|
||||
MessageBox.Show("Bitte wählen Sie ein Installationsverzeichnis.",
|
||||
"Validierung", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
65
EngineeringSync.Setup/Views/Pages/InstallingPage.xaml
Normal file
65
EngineeringSync.Setup/Views/Pages/InstallingPage.xaml
Normal file
@@ -0,0 +1,65 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.InstallingPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White"
|
||||
Loaded="Page_Loaded">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Installation läuft..."
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" x:Name="SubtitleText"
|
||||
Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Bitte warten Sie, während EngineeringSync installiert wird."/>
|
||||
|
||||
<!-- Fortschrittsbereich -->
|
||||
<StackPanel Grid.Row="2" VerticalAlignment="Center">
|
||||
|
||||
<!-- Aktueller Schritt -->
|
||||
<TextBlock x:Name="StepText"
|
||||
FontFamily="Segoe UI" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="#1A1A1A" Margin="0,0,0,8" Text="Starte Installation..."/>
|
||||
|
||||
<!-- Fortschrittsbalken -->
|
||||
<ProgressBar x:Name="ProgressBar"
|
||||
Style="{StaticResource ModernProgressBarStyle}"
|
||||
Minimum="0" Maximum="100" Value="0"
|
||||
Margin="0,0,0,4"/>
|
||||
|
||||
<Grid Margin="0,0,0,24">
|
||||
<TextBlock x:Name="ProgressText" FontFamily="Segoe UI" FontSize="11"
|
||||
Foreground="#5F5F5F" HorizontalAlignment="Left" Text="0%"/>
|
||||
<TextBlock x:Name="TimeText" FontFamily="Segoe UI" FontSize="11"
|
||||
Foreground="#5F5F5F" HorizontalAlignment="Right" Text=""/>
|
||||
</Grid>
|
||||
|
||||
<!-- Live-Log -->
|
||||
<Border Background="#1E1E1E" CornerRadius="8" Padding="16" Height="180">
|
||||
<ScrollViewer x:Name="LogScrollViewer" VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock x:Name="LogText"
|
||||
FontFamily="Cascadia Code, Consolas, Courier New"
|
||||
FontSize="11" Foreground="#CCCCCC"
|
||||
TextWrapping="Wrap" LineHeight="18"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Erfolgs/Fehler-Banner -->
|
||||
<Border Grid.Row="3" x:Name="ResultBanner"
|
||||
CornerRadius="8" Padding="14,10" Margin="0,16,0,0"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="ResultIcon" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="16" Margin="0,0,10,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="ResultText" FontFamily="Segoe UI" FontSize="12"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
102
EngineeringSync.Setup/Views/Pages/InstallingPage.xaml.cs
Normal file
102
EngineeringSync.Setup/Views/Pages/InstallingPage.xaml.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using EngineeringSync.Setup.Services;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class InstallingPage : WizardPageBase
|
||||
{
|
||||
private readonly InstallerService _installer;
|
||||
private bool _started;
|
||||
|
||||
public InstallingPage(WizardViewModel wizard, InstallerService installer) : base(wizard)
|
||||
{
|
||||
_installer = installer;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Nur einmal starten (Page kann bei Navigation neu erzeugt werden)
|
||||
if (_started) return;
|
||||
_started = true;
|
||||
|
||||
_installer.Progress += OnProgress;
|
||||
_installer.LogMessage += OnLogMessage;
|
||||
|
||||
try
|
||||
{
|
||||
// Button während Installation deaktivieren
|
||||
Wizard.SetInstallingState(true);
|
||||
|
||||
await _installer.InstallAsync();
|
||||
ShowSuccess();
|
||||
|
||||
// Buttons wieder aktivieren vor Navigation
|
||||
Wizard.SetInstallingState(false);
|
||||
Wizard.NavigateTo(7); // → CompletionPage
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowError(ex.Message);
|
||||
// Buttons wieder aktivieren im Fehlerfall
|
||||
Wizard.SetInstallingState(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_installer.Progress -= OnProgress;
|
||||
_installer.LogMessage -= OnLogMessage;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProgress(int percent, string step)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ProgressBar.Value = percent;
|
||||
ProgressText.Text = $"{percent}%";
|
||||
StepText.Text = step;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnLogMessage(string message)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var ts = DateTime.Now.ToString("HH:mm:ss");
|
||||
LogText.Text += $"[{ts}] {message}\n";
|
||||
LogScrollViewer.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowSuccess()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ResultBanner.Background = new SolidColorBrush(Color.FromRgb(232, 245, 233));
|
||||
ResultIcon.Text = "\uE73E";
|
||||
ResultIcon.Foreground = new SolidColorBrush(Color.FromRgb(16, 124, 16));
|
||||
ResultText.Text = "Installation erfolgreich abgeschlossen.";
|
||||
ResultText.Foreground = new SolidColorBrush(Color.FromRgb(16, 124, 16));
|
||||
ResultBanner.Visibility = Visibility.Visible;
|
||||
SubtitleText.Text = "Alle Komponenten wurden erfolgreich installiert.";
|
||||
ProgressBar.Value = 100;
|
||||
ProgressText.Text = "100%";
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ResultBanner.Background = new SolidColorBrush(Color.FromRgb(255, 235, 238));
|
||||
ResultIcon.Text = "\uE783";
|
||||
ResultIcon.Foreground = new SolidColorBrush(Color.FromRgb(196, 43, 28));
|
||||
ResultText.Text = $"Fehler: {message}";
|
||||
ResultText.Foreground = new SolidColorBrush(Color.FromRgb(196, 43, 28));
|
||||
ResultBanner.Visibility = Visibility.Visible;
|
||||
SubtitleText.Text = "Die Installation konnte nicht abgeschlossen werden.";
|
||||
});
|
||||
}
|
||||
}
|
||||
32
EngineeringSync.Setup/Views/Pages/OptionCard.xaml
Normal file
32
EngineeringSync.Setup/Views/Pages/OptionCard.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<UserControl x:Class="EngineeringSync.Setup.Views.Pages.OptionCard"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Margin="0,0,0,8">
|
||||
<Border x:Name="CardBorder" Background="White" BorderThickness="1"
|
||||
BorderBrush="#E0E0E0" CornerRadius="8" Padding="16,12"
|
||||
Cursor="Hand" MouseLeftButtonDown="Card_Click">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Width="32" Height="32" CornerRadius="8" Background="#F0F7FF">
|
||||
<TextBlock x:Name="IconText" FontFamily="Segoe MDL2 Assets" FontSize="15"
|
||||
Foreground="#0078D4" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="12,0">
|
||||
<TextBlock x:Name="TitleText" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A"/>
|
||||
<TextBlock x:Name="DescText" FontFamily="Segoe UI" FontSize="11"
|
||||
Foreground="#5F5F5F" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox x:Name="ToggleBox" Grid.Column="2"
|
||||
Style="{StaticResource ModernCheckBoxStyle}"
|
||||
VerticalAlignment="Center" IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
47
EngineeringSync.Setup/Views/Pages/OptionCard.xaml.cs
Normal file
47
EngineeringSync.Setup/Views/Pages/OptionCard.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class OptionCard : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty IconProperty =
|
||||
DependencyProperty.Register(nameof(Icon), typeof(string), typeof(OptionCard),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((OptionCard)d).IconText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(OptionCard),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((OptionCard)d).TitleText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty =
|
||||
DependencyProperty.Register(nameof(Description), typeof(string), typeof(OptionCard),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((OptionCard)d).DescText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty IsCheckedProperty =
|
||||
DependencyProperty.Register(nameof(IsChecked), typeof(bool), typeof(OptionCard),
|
||||
new FrameworkPropertyMetadata(true,
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(d, e) => ((OptionCard)d).UpdateVisual((bool)e.NewValue)));
|
||||
|
||||
public string Icon { get => (string)GetValue(IconProperty); set => SetValue(IconProperty, value); }
|
||||
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public string Description { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); }
|
||||
public bool IsChecked { get => (bool)GetValue(IsCheckedProperty); set => SetValue(IsCheckedProperty, value); }
|
||||
|
||||
public OptionCard() => InitializeComponent();
|
||||
|
||||
private void Card_Click(object sender, RoutedEventArgs e) => IsChecked = !IsChecked;
|
||||
|
||||
private void UpdateVisual(bool isChecked)
|
||||
{
|
||||
if (ToggleBox is null) return;
|
||||
ToggleBox.IsChecked = isChecked;
|
||||
CardBorder.BorderBrush = isChecked
|
||||
? new SolidColorBrush(Color.FromRgb(0, 120, 212))
|
||||
: new SolidColorBrush(Color.FromRgb(224, 224, 224));
|
||||
CardBorder.Background = isChecked
|
||||
? new SolidColorBrush(Color.FromArgb(15, 0, 120, 212))
|
||||
: new SolidColorBrush(Colors.White);
|
||||
}
|
||||
}
|
||||
51
EngineeringSync.Setup/Views/Pages/ServiceOptionsPage.xaml
Normal file
51
EngineeringSync.Setup/Views/Pages/ServiceOptionsPage.xaml
Normal file
@@ -0,0 +1,51 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.ServiceOptionsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Start- und Verknüpfungs-Optionen"
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Legen Sie fest, wie EngineeringSync beim Start von Windows und nach der Installation verhalten soll."/>
|
||||
|
||||
<StackPanel Grid.Row="2">
|
||||
|
||||
<!-- Autostart-Gruppe -->
|
||||
<TextBlock Text="AUTOSTART" Style="{StaticResource FieldLabelStyle}" Margin="0,0,0,10"/>
|
||||
|
||||
<local:OptionCard Icon="" Title="Windows-Dienst automatisch starten"
|
||||
Description="Der EngineeringSync-Dienst startet automatisch mit Windows (empfohlen)"
|
||||
IsChecked="{Binding AutoStartService, Mode=TwoWay}"/>
|
||||
|
||||
<local:OptionCard Icon="" Title="Tray-App bei Anmeldung starten"
|
||||
Description="Die Benachrichtigungs-App erscheint automatisch im Systemtray nach dem Login"
|
||||
IsChecked="{Binding AutoStartTrayApp, Mode=TwoWay}"/>
|
||||
|
||||
<!-- Verknüpfungen -->
|
||||
<TextBlock Text="VERKNÜPFUNGEN" Style="{StaticResource FieldLabelStyle}" Margin="0,20,0,10"/>
|
||||
|
||||
<local:OptionCard Icon="" Title="Desktop-Verknüpfung erstellen"
|
||||
Description="Erstellt eine Verknüpfung zur Tray-App auf dem Desktop"
|
||||
IsChecked="{Binding CreateDesktopShortcut, Mode=TwoWay}"/>
|
||||
|
||||
<local:OptionCard Icon="" Title="Startmenü-Eintrag erstellen"
|
||||
Description="Fügt EngineeringSync dem Windows-Startmenü hinzu"
|
||||
IsChecked="{Binding CreateStartMenuEntry, Mode=TwoWay}"/>
|
||||
|
||||
<!-- Nach Installation -->
|
||||
<TextBlock Text="NACH INSTALLATION" Style="{StaticResource FieldLabelStyle}" Margin="0,20,0,10"/>
|
||||
|
||||
<local:OptionCard Icon="" Title="Tray-App nach Installation starten"
|
||||
Description="Startet die Tray-App direkt nach Abschluss der Installation"
|
||||
IsChecked="{Binding StartAfterInstall, Mode=TwoWay}"/>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
11
EngineeringSync.Setup/Views/Pages/ServiceOptionsPage.xaml.cs
Normal file
11
EngineeringSync.Setup/Views/Pages/ServiceOptionsPage.xaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class ServiceOptionsPage : WizardPageBase
|
||||
{
|
||||
public ServiceOptionsPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
18
EngineeringSync.Setup/Views/Pages/SummaryBoolRow.xaml
Normal file
18
EngineeringSync.Setup/Views/Pages/SummaryBoolRow.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<UserControl x:Class="EngineeringSync.Setup.Views.Pages.SummaryBoolRow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Margin="0,2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="140"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="LabelText" Grid.Column="0"
|
||||
FontFamily="Segoe UI" FontSize="12" Foreground="#5F5F5F"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<TextBlock x:Name="CheckIcon" FontFamily="Segoe MDL2 Assets" FontSize="11"
|
||||
Margin="0,0,6,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="ValueText" FontFamily="Segoe UI" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
40
EngineeringSync.Setup/Views/Pages/SummaryBoolRow.xaml.cs
Normal file
40
EngineeringSync.Setup/Views/Pages/SummaryBoolRow.xaml.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class SummaryBoolRow : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty LabelProperty =
|
||||
DependencyProperty.Register(nameof(Label), typeof(string), typeof(SummaryBoolRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((SummaryBoolRow)d).LabelText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(bool), typeof(SummaryBoolRow),
|
||||
new PropertyMetadata(false, (d, e) => ((SummaryBoolRow)d).UpdateVisual((bool)e.NewValue)));
|
||||
|
||||
public string Label { get => (string)GetValue(LabelProperty); set => SetValue(LabelProperty, value); }
|
||||
public bool Value { get => (bool)GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
|
||||
|
||||
public SummaryBoolRow() => InitializeComponent();
|
||||
|
||||
private void UpdateVisual(bool isOn)
|
||||
{
|
||||
if (CheckIcon is null) return;
|
||||
if (isOn)
|
||||
{
|
||||
CheckIcon.Text = "\uE73E";
|
||||
CheckIcon.Foreground = new SolidColorBrush(Color.FromRgb(16, 124, 16));
|
||||
ValueText.Text = "Ja";
|
||||
ValueText.Foreground = new SolidColorBrush(Color.FromRgb(16, 124, 16));
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckIcon.Text = "\uE711";
|
||||
CheckIcon.Foreground = new SolidColorBrush(Color.FromRgb(160, 160, 160));
|
||||
ValueText.Text = "Nein";
|
||||
ValueText.Foreground = new SolidColorBrush(Color.FromRgb(160, 160, 160));
|
||||
}
|
||||
}
|
||||
}
|
||||
92
EngineeringSync.Setup/Views/Pages/SummaryPage.xaml
Normal file
92
EngineeringSync.Setup/Views/Pages/SummaryPage.xaml
Normal file
@@ -0,0 +1,92 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.SummaryPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Zusammenfassung" Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Grid.Row="1" Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Überprüfen Sie Ihre Einstellungen. Klicken Sie auf "Jetzt installieren" um die Installation zu starten."/>
|
||||
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
|
||||
<!-- Installation -->
|
||||
<Border Style="{StaticResource InfoCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="#0078D4" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Installation" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<local:SummaryRow Label="Installationspfad" Value="{Binding InstallPath}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Erstes Projekt -->
|
||||
<Border Style="{StaticResource InfoCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="#0078D4" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Erstes Projekt" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<local:SummaryRow Label="Projektname" Value="{Binding ProjectName}"/>
|
||||
<local:SummaryRow Label="Engineering-Pfad" Value="{Binding EngineeringPath}"/>
|
||||
<local:SummaryRow Label="Simulations-Pfad" Value="{Binding SimulationPath}"/>
|
||||
<local:SummaryRow Label="Dateiendungen" Value="{Binding FileExtensions}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Backup -->
|
||||
<Border Style="{StaticResource InfoCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="#0078D4" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Backup" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<local:SummaryBoolRow Label="Backups aktiviert" Value="{Binding BackupEnabled}"/>
|
||||
<local:SummaryRow Label="Backup-Ordner"
|
||||
Value="{Binding BackupCustomPath}"
|
||||
Visibility="{Binding BackupUseCustomPath,
|
||||
Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<!-- Aufbewahrung immer anzeigen wenn Backup aktiv (0 = unbegrenzt ist ein valider Wert) -->
|
||||
<local:SummaryRow Label="Max. Backups/Datei"
|
||||
Value="{Binding MaxBackupsPerFile,
|
||||
StringFormat='{}{0} (0=unbegrenzt)'}"
|
||||
Visibility="{Binding BackupEnabled,
|
||||
Converter={StaticResource BoolToVisConverter}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Optionen -->
|
||||
<Border Style="{StaticResource InfoCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="#0078D4" Margin="0,0,8,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Optionen" FontFamily="Segoe UI" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1A1A1A" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<local:SummaryBoolRow Label="Dienst automatisch starten" Value="{Binding AutoStartService}"/>
|
||||
<local:SummaryBoolRow Label="Tray-App bei Anmeldung" Value="{Binding AutoStartTrayApp}"/>
|
||||
<local:SummaryBoolRow Label="Desktop-Verknüpfung" Value="{Binding CreateDesktopShortcut}"/>
|
||||
<local:SummaryBoolRow Label="Startmenü-Eintrag" Value="{Binding CreateStartMenuEntry}"/>
|
||||
<local:SummaryBoolRow Label="Tray-App nach Installation" Value="{Binding StartAfterInstall}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
11
EngineeringSync.Setup/Views/Pages/SummaryPage.xaml.cs
Normal file
11
EngineeringSync.Setup/Views/Pages/SummaryPage.xaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class SummaryPage : WizardPageBase
|
||||
{
|
||||
public SummaryPage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
16
EngineeringSync.Setup/Views/Pages/SummaryRow.xaml
Normal file
16
EngineeringSync.Setup/Views/Pages/SummaryRow.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="EngineeringSync.Setup.Views.Pages.SummaryRow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Margin="0,2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="140"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="LabelText" Grid.Column="0"
|
||||
FontFamily="Segoe UI" FontSize="12" Foreground="#5F5F5F"/>
|
||||
<TextBlock x:Name="ValueText" Grid.Column="1"
|
||||
FontFamily="Segoe UI" FontSize="12" Foreground="#1A1A1A"
|
||||
TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
19
EngineeringSync.Setup/Views/Pages/SummaryRow.xaml.cs
Normal file
19
EngineeringSync.Setup/Views/Pages/SummaryRow.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class SummaryRow : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty LabelProperty =
|
||||
DependencyProperty.Register(nameof(Label), typeof(string), typeof(SummaryRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((SummaryRow)d).LabelText.Text = (string)e.NewValue));
|
||||
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(string), typeof(SummaryRow),
|
||||
new PropertyMetadata(string.Empty, (d, e) => ((SummaryRow)d).ValueText.Text = (string)e.NewValue));
|
||||
|
||||
public string Label { get => (string)GetValue(LabelProperty); set => SetValue(LabelProperty, value); }
|
||||
public string Value { get => (string)GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
|
||||
public SummaryRow() => InitializeComponent();
|
||||
}
|
||||
63
EngineeringSync.Setup/Views/Pages/WelcomePage.xaml
Normal file
63
EngineeringSync.Setup/Views/Pages/WelcomePage.xaml
Normal file
@@ -0,0 +1,63 @@
|
||||
<local:WizardPageBase x:Class="EngineeringSync.Setup.Views.Pages.WelcomePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:EngineeringSync.Setup.Views.Pages"
|
||||
Background="White">
|
||||
<Grid Margin="40,32,40,24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Großes Begrüßungs-Icon -->
|
||||
<Border Grid.Row="0" Width="64" Height="64" CornerRadius="16"
|
||||
HorizontalAlignment="Left" Margin="0,0,0,24">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#0078D4" Offset="0"/>
|
||||
<GradientStop Color="#00B4FF" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="32"
|
||||
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Überschrift -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,20">
|
||||
<TextBlock Text="Willkommen bei EngineeringSync"
|
||||
Style="{StaticResource PageTitleStyle}"/>
|
||||
<TextBlock Style="{StaticResource PageSubtitleStyle}"
|
||||
Text="Dieser Assistent führt Sie durch die Installation und Erstkonfiguration von EngineeringSync – dem intelligenten Bindeglied zwischen Ihrem Engineering- und Simulationsumfeld."/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Feature-Liste -->
|
||||
<StackPanel Grid.Row="2" VerticalAlignment="Top">
|
||||
<TextBlock Text="Was wird installiert:"
|
||||
FontFamily="Segoe UI" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="#5F5F5F" Margin="0,0,0,12"/>
|
||||
|
||||
<local:FeatureRow Icon="" Title="EngineeringSync Service"
|
||||
Description="Windows-Dienst der Ihr Engineering-Verzeichnis überwacht und Änderungen protokolliert"/>
|
||||
<local:FeatureRow Icon="" Title="System Tray App"
|
||||
Description="Benachrichtigt Sie sofort über neue Änderungen und ermöglicht kontrollierten Sync"/>
|
||||
<local:FeatureRow Icon="" Title="Konfigurations-Wizard"
|
||||
Description="Verwalten Sie Projekte und Sync-Pfade bequem per grafischer Oberfläche"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- System-Check -->
|
||||
<Border Grid.Row="3" Background="#F0F7FF" CornerRadius="8" Padding="14,10" Margin="0,16,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"
|
||||
Foreground="#0078D4" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<TextBlock FontFamily="Segoe UI" FontSize="12" Foreground="#5F5F5F"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap">
|
||||
<Run Text="Klicken Sie auf " FontWeight="Regular"/>
|
||||
<Run Text="Weiter" FontWeight="SemiBold"/>
|
||||
<Run Text=" um die Installation zu beginnen." FontWeight="Regular"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</local:WizardPageBase>
|
||||
11
EngineeringSync.Setup/Views/Pages/WelcomePage.xaml.cs
Normal file
11
EngineeringSync.Setup/Views/Pages/WelcomePage.xaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
public partial class WelcomePage : WizardPageBase
|
||||
{
|
||||
public WelcomePage(WizardViewModel wizard) : base(wizard)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
25
EngineeringSync.Setup/Views/Pages/WizardPageBase.cs
Normal file
25
EngineeringSync.Setup/Views/Pages/WizardPageBase.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Windows.Controls;
|
||||
using EngineeringSync.Setup.ViewModels;
|
||||
|
||||
namespace EngineeringSync.Setup.Views.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Basisklasse für alle Wizard-Seiten.
|
||||
/// Stellt gemeinsame WizardViewModel-Referenz und Validierungsschnittstelle bereit.
|
||||
/// </summary>
|
||||
public abstract class WizardPageBase : UserControl
|
||||
{
|
||||
protected WizardViewModel Wizard { get; }
|
||||
|
||||
protected WizardPageBase(WizardViewModel wizard)
|
||||
{
|
||||
Wizard = wizard;
|
||||
DataContext = wizard.State;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wird vor dem Vorwärtsnavigieren aufgerufen.
|
||||
/// Gibt false zurück um die Navigation zu blockieren (z.B. Validierungsfehler).
|
||||
/// </summary>
|
||||
public virtual bool Validate() => true;
|
||||
}
|
||||
251
EngineeringSync.Setup/Views/WizardWindow.xaml
Normal file
251
EngineeringSync.Setup/Views/WizardWindow.xaml
Normal file
@@ -0,0 +1,251 @@
|
||||
<Window x:Class="EngineeringSync.Setup.Views.WizardWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:EngineeringSync.Setup.ViewModels"
|
||||
Title="EngineeringSync Setup"
|
||||
Width="860" Height="580"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent">
|
||||
|
||||
<Window.DataContext>
|
||||
<vm:WizardViewModel/>
|
||||
</Window.DataContext>
|
||||
|
||||
<!-- Äußerer Schatten-Container -->
|
||||
<Border CornerRadius="10"
|
||||
Background="White">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#40000000" BlurRadius="30" ShadowDepth="0" Opacity="0.4"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="260"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ═══════════════════════════════
|
||||
LINKE SIDEBAR
|
||||
═══════════════════════════════ -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{StaticResource SidebarGradient}"
|
||||
CornerRadius="10,0,0,10">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Logo-Bereich -->
|
||||
<StackPanel Grid.Row="0" Margin="24,32,24,32">
|
||||
<!-- App-Icon (Segoe MDL2 Gear-ähnliches Symbol) -->
|
||||
<Border Width="52" Height="52"
|
||||
CornerRadius="12"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,0,0,16">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#0078D4" Offset="0"/>
|
||||
<GradientStop Color="#00B4FF" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="26"
|
||||
Foreground="White"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="EngineeringSync"
|
||||
FontFamily="Segoe UI"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="White"/>
|
||||
<TextBlock Text="Setup-Assistent"
|
||||
FontFamily="Segoe UI"
|
||||
FontSize="11"
|
||||
Foreground="#7788AA"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Schritt-Indikatoren -->
|
||||
<ItemsControl Grid.Row="1"
|
||||
ItemsSource="{Binding Steps}"
|
||||
Margin="20,0,20,0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Style="{StaticResource StepItemStyle}"
|
||||
Content="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Version -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="Version 1.0.0"
|
||||
FontFamily="Segoe UI"
|
||||
FontSize="10"
|
||||
Foreground="#445566"
|
||||
Margin="24,0,24,20"
|
||||
HorizontalAlignment="Left"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ═══════════════════════════════
|
||||
RECHTE INHALTSSEITE + NAVIGATION
|
||||
═══════════════════════════════ -->
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Custom Title Bar -->
|
||||
<RowDefinition Height="*"/> <!-- Page Content -->
|
||||
<RowDefinition Height="Auto"/> <!-- Navigation Bar -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Custom Title Bar (Drag + Schließen) -->
|
||||
<Border Grid.Row="0"
|
||||
Height="40"
|
||||
CornerRadius="0,10,0,0"
|
||||
MouseLeftButtonDown="TitleBar_MouseDown">
|
||||
<Grid>
|
||||
<TextBlock Text="EngineeringSync Setup"
|
||||
FontFamily="Segoe UI"
|
||||
FontSize="12"
|
||||
Foreground="#888888"
|
||||
VerticalAlignment="Center"
|
||||
Margin="16,0,0,0"/>
|
||||
<Button Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Width="40" Height="40"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="#888888"
|
||||
Cursor="Hand"
|
||||
Click="CloseButton_Click"
|
||||
ToolTip="Schließen">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="bg"
|
||||
Background="Transparent"
|
||||
CornerRadius="0,10,0,0">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bg" Property="Background" Value="#FFE81123"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ── Separator ── -->
|
||||
<Border Grid.Row="0" Height="1" Background="{StaticResource BorderBrush}"
|
||||
VerticalAlignment="Bottom"/>
|
||||
|
||||
<!-- Seiten-Content mit Animation -->
|
||||
<ContentPresenter Grid.Row="1"
|
||||
Content="{Binding CurrentPage}"
|
||||
Margin="0"/>
|
||||
|
||||
<!-- ── Separator ── -->
|
||||
<Border Grid.Row="2" Height="1" Background="{StaticResource BorderBrush}"
|
||||
VerticalAlignment="Top"/>
|
||||
|
||||
<!-- Navigation Bar -->
|
||||
<Border Grid.Row="2"
|
||||
Background="#FAFAFA"
|
||||
CornerRadius="0,0,10,0"
|
||||
Padding="24,12">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Abbrechen (links) -->
|
||||
<Button Grid.Column="0"
|
||||
Content="Abbrechen"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Command="{Binding CancelCommand}"
|
||||
Visibility="{Binding IsCompleted,
|
||||
Converter={StaticResource BoolToInvVisConverter}}"/>
|
||||
|
||||
<!-- Zurück -->
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Margin="0,0,8,0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Visibility="{Binding CanGoBack,
|
||||
Converter={StaticResource BoolToVisConverter}}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Zurück"
|
||||
FontFamily="Segoe UI"/>
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<!-- Weiter / Installieren -->
|
||||
<Button Grid.Column="2"
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
Command="{Binding GoNextCommand}"
|
||||
Visibility="{Binding ShowNextButton,
|
||||
Converter={StaticResource BoolToVisConverter}}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="nextLabel"
|
||||
Text="{Binding IsLastStep,
|
||||
Converter={StaticResource LastStepLabelConverter}}"/>
|
||||
<TextBlock Text=" "
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{Binding IsLastStep,
|
||||
Converter={StaticResource BoolToInvVisConverter}}"/>
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<!-- Fertig-Button -->
|
||||
<Button Grid.Column="3"
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
Command="{Binding FinishCommand}"
|
||||
Visibility="{Binding IsCompleted,
|
||||
Converter={StaticResource BoolToVisConverter}}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Schließen"
|
||||
FontFamily="Segoe UI"/>
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
24
EngineeringSync.Setup/Views/WizardWindow.xaml.cs
Normal file
24
EngineeringSync.Setup/Views/WizardWindow.xaml.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace EngineeringSync.Setup.Views;
|
||||
|
||||
public partial class WizardWindow : Window
|
||||
{
|
||||
public WizardWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
DragMove();
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var vm = (ViewModels.WizardViewModel)DataContext;
|
||||
vm.CancelCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
25
EngineeringSync.Setup/app.manifest
Normal file
25
EngineeringSync.Setup/app.manifest
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="EngineeringSync.Setup.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- Adminrechte für Service-Installation erforderlich -->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 11 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user