Desktop-Icons in eigene Kategorien sortieren: Windows-11-Design (hell/dunkel), Desktop-Boxen, automatisches Einsortieren nach Regeln, Rückgängig, Suche, Infobereich mit Tastenkürzel, Sicherung als ZIP und Installer ohne Adminrechte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.7 KiB
C#
130 lines
5.7 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
namespace DeskCore;
|
|
|
|
/// <summary>Dialoge im Stil eines Windows-11-ContentDialogs.</summary>
|
|
public static class Dialog
|
|
{
|
|
public static bool Confirm(Window owner, string title, string message, string primary = "OK", string? secondary = "Abbrechen") =>
|
|
Show(owner, title, Text(message), primary, secondary, null);
|
|
|
|
public static void Info(Window owner, string title, string message) =>
|
|
Show(owner, title, Text(message), "OK", null, null);
|
|
|
|
/// <summary>Eingabedialog. validate gibt eine Fehlermeldung oder null zurück.</summary>
|
|
public static string? Prompt(Window owner, string title, string label, string initial, string primary, Func<string, string?> validate)
|
|
{
|
|
var box = new TextBox { Text = initial, Margin = new Thickness(0, 8, 0, 0) };
|
|
var panel = new StackPanel();
|
|
panel.Children.Add(Text(label, secondary: true));
|
|
panel.Children.Add(box);
|
|
var ok = Show(owner, title, panel, primary, "Abbrechen", () => validate(box.Text), box);
|
|
return ok ? box.Text.Trim() : null;
|
|
}
|
|
|
|
public static TextBlock Text(string text, bool secondary = false) => new()
|
|
{
|
|
Text = text, TextWrapping = TextWrapping.Wrap, LineHeight = 20,
|
|
Foreground = (Brush)Application.Current.Resources[secondary ? "TextSecondaryBrush" : "TextBrush"],
|
|
};
|
|
|
|
/// <summary>Allgemeiner Dialog mit beliebigem Inhalt. validate: Fehlermeldung oder null.</summary>
|
|
public static bool Show(Window owner, string title, UIElement content, string primary, string? secondary,
|
|
Func<string?>? validate, IInputElement? focus = null, double width = 448)
|
|
{
|
|
var res = Application.Current.Resources;
|
|
|
|
var error = new TextBlock
|
|
{
|
|
Foreground = (Brush)res["DangerBrush"], FontSize = 12, Margin = new Thickness(0, 8, 0, 0),
|
|
TextWrapping = TextWrapping.Wrap, Visibility = Visibility.Collapsed,
|
|
};
|
|
|
|
var body = new StackPanel { Margin = new Thickness(24, 22, 24, 24) };
|
|
body.Children.Add(new TextBlock
|
|
{
|
|
Text = title, FontSize = 20, FontWeight = FontWeights.SemiBold, Margin = new Thickness(0, 0, 0, 12),
|
|
FontFamily = (FontFamily)res["DisplayFont"], TextWrapping = TextWrapping.Wrap,
|
|
});
|
|
body.Children.Add(content);
|
|
body.Children.Add(error);
|
|
|
|
var ok = new Button { Content = primary, IsDefault = true, Style = (Style)res["AccentButton"] };
|
|
var footer = new Grid { Margin = new Thickness(24) };
|
|
footer.ColumnDefinitions.Add(new ColumnDefinition());
|
|
footer.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(8) });
|
|
footer.ColumnDefinitions.Add(new ColumnDefinition());
|
|
if (secondary is not null)
|
|
{
|
|
var cancel = new Button { Content = secondary, IsCancel = true };
|
|
Grid.SetColumn(cancel, 2);
|
|
footer.Children.Add(cancel);
|
|
}
|
|
else Grid.SetColumn(ok, 2); // einzelner Button rechts, wie bei Windows 11
|
|
footer.Children.Add(ok);
|
|
|
|
var layout = new DockPanel();
|
|
var footerBorder = new Border
|
|
{
|
|
Background = (Brush)res["DialogFooterBrush"], Child = footer,
|
|
BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(0, 1, 0, 0),
|
|
CornerRadius = new CornerRadius(0, 0, 8, 8),
|
|
};
|
|
DockPanel.SetDock(footerBorder, Dock.Bottom);
|
|
layout.Children.Add(footerBorder);
|
|
layout.Children.Add(new ScrollViewer { Content = body, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, MaxHeight = 560 });
|
|
|
|
var dlg = new Window
|
|
{
|
|
Owner = owner.IsVisible ? owner : null, Title = title, Width = width + 64, SizeToContent = SizeToContent.Height,
|
|
WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = Brushes.Transparent,
|
|
ResizeMode = ResizeMode.NoResize, ShowInTaskbar = !owner.IsVisible, Topmost = !owner.IsVisible,
|
|
WindowStartupLocation = owner.IsVisible ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen,
|
|
FontFamily = (FontFamily)res["UiFont"], FontSize = 14,
|
|
Foreground = (Brush)res["TextBrush"],
|
|
Content = new Border
|
|
{
|
|
Margin = new Thickness(32), CornerRadius = new CornerRadius(8),
|
|
Background = (Brush)res["LayerBrush"],
|
|
BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
|
|
Effect = (System.Windows.Media.Effects.Effect)res["DialogShadow"], Child = layout,
|
|
},
|
|
};
|
|
|
|
ok.Click += (_, _) =>
|
|
{
|
|
if (validate?.Invoke() is { } err)
|
|
{
|
|
error.Text = err;
|
|
error.Visibility = Visibility.Visible;
|
|
(focus as UIElement)?.Focus();
|
|
return;
|
|
}
|
|
dlg.DialogResult = true;
|
|
};
|
|
if (focus is TextBox tb) tb.TextChanged += (_, _) => error.Visibility = Visibility.Collapsed;
|
|
|
|
dlg.PreviewKeyDown += (_, e) => { if (e.Key == Key.Escape) dlg.Close(); };
|
|
dlg.MouseLeftButtonDown += (_, e) => { if (e.ButtonState == MouseButtonState.Pressed) dlg.DragMove(); };
|
|
dlg.Loaded += (_, _) =>
|
|
{
|
|
if (focus is TextBox t) { t.Focus(); t.SelectAll(); }
|
|
else if (focus is UIElement u) u.Focus();
|
|
else ok.Focus();
|
|
};
|
|
|
|
(owner as MainWindow)?.SetDimmed(true);
|
|
try
|
|
{
|
|
return dlg.ShowDialog() == true;
|
|
}
|
|
finally
|
|
{
|
|
(owner as MainWindow)?.SetDimmed(false);
|
|
}
|
|
}
|
|
}
|