66 Commits
Author SHA1 Message Date
M_Viper 0f48097707 Upload via GUI (186 Dateien) 2026-10-04 14:48:59 +00:00
M_Viper 0d7ff9e9c3 Upload via GUI (186 Dateien) 2026-10-04 14:48:57 +00:00
M_Viper 70c3b8288b Upload via GUI (186 Dateien) 2026-10-04 14:48:55 +00:00
M_Viper 8efd5bc497 Upload via GUI (186 Dateien) 2026-10-04 14:48:53 +00:00
M_Viper c0b552ef39 Delete Setup/obj/project.nuget.cache via Git Manager GUI 2026-10-04 14:48:30 +00:00
M_Viper 58461c1211 Delete Setup/obj/Release/net9.0-windows/win-x64/refint/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:29 +00:00
M_Viper 5c4fe0a029 Delete Setup/obj/Release/net9.0-windows/win-x64/apphost.exe via Git Manager GUI 2026-10-04 14:48:29 +00:00
M_Viper 51bfaa73b0 Delete Setup/obj/Release/net9.0-windows/win-x64/MainWindow.g.cs via Git Manager GUI 2026-10-04 14:48:28 +00:00
M_Viper fcc0bedfab Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genruntimeconfig.cache via Git Manager GUI 2026-10-04 14:48:28 +00:00
M_Viper c78a8cc6f1 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genbundle.cache via Git Manager GUI 2026-10-04 14:48:27 +00:00
M_Viper 624d9c7a79 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.csproj.CoreCompileInputs.cache via Git Manager GUI 2026-10-04 14:48:27 +00:00
M_Viper 18128817d4 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper 5b6f6881ee Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.AssemblyInfoInputs.cache via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper 5058ceb6e5 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup_MarkupCompile.cache via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper c22bea3b39 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:25 +00:00
M_Viper 56352ede19 Delete Setup/obj/Release/net9.0-windows/win-x64/Controls.baml via Git Manager GUI 2026-10-04 14:48:25 +00:00
M_Viper e62822a544 Delete Setup/obj/Release/net9.0-windows/win-x64/Colors.Dark.baml via Git Manager GUI 2026-10-04 14:48:24 +00:00
M_Viper 100caee6ed Delete Setup/obj/Release/net9.0-windows/win-x64/App.baml via Git Manager GUI 2026-10-04 14:48:24 +00:00
M_Viper 3ed91b8406 Delete Setup/obj/Release/net48/MainWindow.g.cs via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper d8c18425fe Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.FileListAbsolute.txt via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper b7c38cd380 Delete Setup/obj/Release/net48/DeskCoreSetup.assets.cache via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper eb8c33e990 Delete Setup/obj/Release/net48/DeskCoreSetup.GlobalUsings.g.cs via Git Manager GUI 2026-10-04 14:48:22 +00:00
M_Viper 8638b115e8 Delete Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfoInputs.cache via Git Manager GUI 2026-10-04 14:48:22 +00:00
M_Viper 33cfe843f1 Delete Setup/obj/Release/net48/DeskCore-Setup_MarkupCompile.cache via Git Manager GUI 2026-10-04 14:48:21 +00:00
M_Viper f4ff96a0e4 Delete Setup/obj/Release/net48/DeskCore-Setup.exe.withSupportedRuntime.config via Git Manager GUI 2026-10-04 14:48:21 +00:00
M_Viper 483af97e6b Delete Setup/obj/Release/net48/Controls.baml via Git Manager GUI 2026-10-04 14:48:20 +00:00
M_Viper 6d610ca447 Delete Setup/obj/Release/net48/Colors.Dark.baml via Git Manager GUI 2026-10-04 14:48:20 +00:00
M_Viper d87c360d2d Delete Setup/obj/Release/net48/App.baml via Git Manager GUI 2026-10-04 14:48:19 +00:00
M_Viper 33eaacc293 Delete Setup/obj/DeskCoreSetup.csproj.nuget.g.targets via Git Manager GUI 2026-10-04 14:48:19 +00:00
M_Viper 28b031ba58 Delete Setup/obj/DeskCoreSetup.csproj.nuget.dgspec.json via Git Manager GUI 2026-10-04 14:48:18 +00:00
M_Viper 99a1a45c7a Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.exe via Git Manager GUI 2026-10-04 14:48:18 +00:00
M_Viper 6fcacbb577 Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.deps.json via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper eec5f2a4ca Delete Setup/MainWindow.xaml.cs via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper 20b0812c82 Delete Setup/Installer.cs via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper 0462d3fae2 Delete Setup/App.xaml.cs via Git Manager GUI 2026-10-04 14:48:16 +00:00
M_Viper 020c83180c Delete Setup/obj/project.assets.json via Git Manager GUI 2026-10-04 14:48:14 +00:00
M_Viper 6791a114b2 Delete Setup/obj/Release/net9.0-windows/win-x64/ref/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:14 +00:00
M_Viper 7183e39d21 Delete Setup/obj/Release/net9.0-windows/win-x64/PublishOutputs.02abb2eb9c.txt via Git Manager GUI 2026-10-04 14:48:13 +00:00
M_Viper d8f6a70c2c Delete Setup/obj/Release/net9.0-windows/win-x64/MainWindow.baml via Git Manager GUI 2026-10-04 14:48:13 +00:00
M_Viper ba0d990d69 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genpublishdeps.cache via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 9a5e954b73 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.csproj.FileListAbsolute.txt via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 5cb7224a2d Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.assets.cache via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 0318a3dc20 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.GlobalUsings.g.cs via Git Manager GUI 2026-10-04 14:48:11 +00:00
M_Viper 6b713b08ad Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.AssemblyInfo.cs via Git Manager GUI 2026-10-04 14:48:11 +00:00
M_Viper 271701bfda Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.g.resources via Git Manager GUI 2026-10-04 14:48:10 +00:00
M_Viper 85f3f597bb Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.deps.json via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper 17dcfb16ea Delete Setup/obj/Release/net9.0-windows/win-x64/Colors.Light.baml via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper 29fe9816a3 Delete Setup/obj/Release/net9.0-windows/win-x64/App.g.cs via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper e170dcd90c Delete Setup/obj/Release/net9.0-windows/win-x64/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs via Git Manager GUI 2026-10-04 14:48:08 +00:00
M_Viper 077eba609e Delete Setup/obj/Release/net48/MainWindow.baml via Git Manager GUI 2026-10-04 14:48:08 +00:00
M_Viper ff8b5b9484 Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.CoreCompileInputs.cache via Git Manager GUI 2026-10-04 14:48:07 +00:00
M_Viper 1e8b538a71 Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.AssemblyReference.cache via Git Manager GUI 2026-10-04 14:48:07 +00:00
M_Viper a1c2bc38ba Delete Setup/obj/Release/net48/DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig via Git Manager GUI 2026-10-04 14:48:06 +00:00
M_Viper 4c44b7769d Delete Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfo.cs via Git Manager GUI 2026-10-04 14:48:06 +00:00
M_Viper 54aee17a69 Delete Setup/obj/Release/net48/DeskCore-Setup.g.resources via Git Manager GUI 2026-10-04 14:48:05 +00:00
M_Viper 55baffb4d8 Delete Setup/obj/Release/net48/DeskCore-Setup.exe via Git Manager GUI 2026-10-04 14:48:05 +00:00
M_Viper 2df0e95fd6 Delete Setup/obj/Release/net48/Colors.Light.baml via Git Manager GUI 2026-10-04 14:48:04 +00:00
M_Viper 84d835828c Delete Setup/obj/Release/net48/App.g.cs via Git Manager GUI 2026-10-04 14:48:04 +00:00
M_Viper a434d48cb0 Delete Setup/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs via Git Manager GUI 2026-10-04 14:48:03 +00:00
M_Viper 97e5b87ef3 Delete Setup/obj/DeskCoreSetup.csproj.nuget.g.props via Git Manager GUI 2026-10-04 14:48:03 +00:00
M_Viper fffc70d1fb Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.runtimeconfig.json via Git Manager GUI 2026-10-04 14:48:02 +00:00
M_Viper 1b0c3ee187 Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:02 +00:00
M_Viper 48ed7eef5c Delete Setup/Runtime.cs via Git Manager GUI 2026-10-04 14:48:01 +00:00
M_Viper ee92f6cb9f Delete Setup/MainWindow.xaml via Git Manager GUI 2026-10-04 14:48:01 +00:00
M_Viper efd6100ab9 Delete Setup/DeskCoreSetup.csproj via Git Manager GUI 2026-10-04 14:48:00 +00:00
M_Viper beee89114f Delete Setup/App.xaml via Git Manager GUI 2026-10-04 14:48:00 +00:00
79 changed files with 3018 additions and 1459 deletions
+19 -1
View File
@@ -14,7 +14,7 @@ public partial class App : Application
private static Hotkey? _hotkey;
private static Hotkey? _peekHotkey;
private static Hotkey? _pagePrev, _pageNext;
private static Hotkey? _pagePrev, _pageNext, _quickSearch;
private static System.Windows.Threading.DispatcherTimer? _desktopTimer;
private static Watcher? _watcher;
private Mutex? _mutex;
@@ -65,6 +65,9 @@ public partial class App : Application
_pageNext = new Hotkey(hwnd, 0xB004);
_pageNext.Pressed += () => FenceManager.NextPage(1);
_pageNext.Register(Hotkey.ModWin | Hotkey.ModAlt, 0x27);
_quickSearch = new Hotkey(hwnd, 0xB005);
_quickSearch.Pressed += QuickSearch.Toggle;
ApplyQuickSearchHotkey();
TrayIcon = new Tray();
TrayIcon.Clicked += () => Win.ShowFromBackground();
@@ -110,6 +113,7 @@ public partial class App : Application
{
var s = AppSettings.Current;
if (Quitting) return;
PageScheduler.Tick();
// Nur lesen, wenn eine Funktion die Positionen braucht
if (s.DesktopTint is null && !s.DrawFences && !s.QuickHide) return;
try { DesktopIcons.RefreshCache(); } catch { /* Explorer gerade neu gestartet o. Ä. */ }
@@ -124,6 +128,7 @@ public partial class App : Application
else if (Commands.Value(args, "--snapshot") is { } snap) Win.RestoreSnapshotByName(snap);
else if (Commands.Has(args, "--toggle-desktop")) DesktopToggle.Toggle();
else if (Commands.Has(args, "--peek")) FenceManager.TogglePeek();
else if (Commands.Has(args, "--search")) QuickSearch.Toggle();
else if (Commands.Value(args, "--page") is { } page) SwitchPageByName(page);
else if (Commands.WantsWindow(args)) Win.ShowFromBackground();
}
@@ -138,6 +143,17 @@ public partial class App : Application
else TrayIcon?.Notify("Seite nicht gefunden", $"Es gibt keine Desktop-Seite „{page}“.");
}
public static bool QuickSearchRegistered { get; private set; }
/// <summary>Tastenkürzel der Schnellstart-Suche neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyQuickSearchHotkey()
{
if (_quickSearch is null) return false;
var s = AppSettings.Current;
if (!s.QuickSearchEnabled) { _quickSearch.Unregister(); return QuickSearchRegistered = true; }
return QuickSearchRegistered = _quickSearch.Register(s.QuickSearchModifiers, s.QuickSearchKey);
}
/// <summary>Tastenkürzel neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyHotkey()
{
@@ -172,6 +188,8 @@ public partial class App : Application
DesktopToggle.RestoreOnExit();
_peekHotkey?.Dispose();
_pagePrev?.Dispose();
_quickSearch?.Dispose();
Usage.Flush();
_pageNext?.Dispose();
Win?.Undo.FlushAll();
FenceManager.CloseAll();
+190
View File
@@ -0,0 +1,190 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace DeskCore;
/// <summary>
/// Auswahl nach dem Aufziehen einer Box: Welche Kategorie (oder welcher Ordner) soll hinein?
/// Ein eigenes Fenster statt eines Kontextmenüs – ein Menü ohne aktives Fenster schließt sich sonst gelegentlich von selbst.
/// </summary>
public sealed class BoxPicker : Window
{
private static BoxPicker? _open;
private readonly long _shownAt = Environment.TickCount64;
private bool _done;
private static AppSettings S => AppSettings.Current;
/// <param name="area">Fläche der neuen Box in WPF-Einheiten.</param>
/// <param name="create">Legt die Box mit dem gewählten Tab an.</param>
public static void Show(Rect area, Action<FenceTab> create)
{
_open?.Finish(null);
_open = new BoxPicker(area, create);
_open.Show();
}
private readonly Action<FenceTab> _create;
private BoxPicker(Rect area, Action<FenceTab> create)
{
_create = create;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Topmost = true;
SizeToContent = SizeToContent.WidthAndHeight;
Title = "Neue Box";
var res = Application.Current.Resources;
var list = new StackPanel();
list.Children.Add(new TextBlock
{
Text = "Neue Box hier", FontSize = 14, FontWeight = FontWeights.SemiBold, Margin = new Thickness(12, 8, 12, 0),
Foreground = (Brush)res["TextBrush"],
});
list.Children.Add(new TextBlock
{
Text = "Was soll hinein?", FontSize = 12, Margin = new Thickness(12, 2, 12, 6),
Foreground = (Brush)res["TextSecondaryBrush"],
});
var cats = Storage.Categories();
var catPanel = new StackPanel();
foreach (var c in cats)
{
var meta = S.Categories.GetValueOrDefault(c);
// Pro Seite steht eine Kategorie nur in einer Box – andere Seiten behalten ihre
string? hint = S.HasFence(c) ? "wird aus ihrer Box auf dieser Seite hierher geholt" : null;
catPanel.Children.Add(Row(meta?.Glyph ?? "", CategoryColors.Brush(meta?.Color), c, hint,
() => Pick(new FenceTab { Value = c })));
}
if (cats.Count > 0)
{
list.Children.Add(new ScrollViewer
{
Content = catPanel, MaxHeight = 360, VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
});
list.Children.Add(Divider());
}
list.Children.Add(Row("", null, "Zuletzt benutzt", "zeigt, was du zuletzt gestartet hast",
() => Pick(new FenceTab { Kind = FenceTab.RecentKind })));
list.Children.Add(Row("", null, "Neue Kategorie …", null, () => PickLater(() =>
App.Win?.CreateCategoryInteractive() is { } name ? new FenceTab { Value = name } : null)));
list.Children.Add(Row("", null, "Ordner-Portal …", null, () => PickLater(() =>
FenceManager.PickFolder() is { } folder ? new FenceTab { Kind = FenceTab.FolderKind, Value = folder } : null)));
list.Children.Add(Divider());
list.Children.Add(Row("", null, "Abbrechen", null, () => Finish(null)));
Content = new Border
{
Margin = new Thickness(14), Padding = new Thickness(4), MinWidth = 240, CornerRadius = new CornerRadius(8),
Background = (Brush)res["FlyoutBrush"], BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
Effect = (System.Windows.Media.Effects.Effect)res["FlyoutShadow"],
Child = list,
};
// Neben dem Mauszeiger öffnen, aber immer ganz auf dem Bildschirm
Loaded += (_, _) =>
{
var mouse = GetCursorPos(out var p) ? new Point(p.X / Screens.Scale, p.Y / Screens.Scale) : area.BottomRight;
var wa = Screens.WorkAreaAt(mouse);
Left = Math.Clamp(mouse.X - 14, wa.Left, Math.Max(wa.Left, wa.Right - ActualWidth));
Top = Math.Clamp(mouse.Y - 14, wa.Top, Math.Max(wa.Top, wa.Bottom - ActualHeight));
BringToFront();
};
KeyDown += (_, e) => { if (e.Key == Key.Escape) Finish(null); };
// Klick daneben schließt – aber nicht durch ein kurzes Flackern direkt nach dem Öffnen
Deactivated += (_, _) =>
{
if (_done) return;
if (Environment.TickCount64 - _shownAt < 400) BringToFront();
else Finish(null);
};
Closed += (_, _) => { if (_open == this) _open = null; DrawBoxOverlay.Hide(); };
}
private Button Row(string glyph, Brush? glyphBrush, string text, string? hint, Action click)
{
var res = Application.Current.Resources;
var content = new DockPanel();
var icon = new TextBlock
{
Text = glyph, FontFamily = (FontFamily)res["IconFont"], FontSize = 15, Width = 20, Margin = new Thickness(0, 0, 12, 0),
VerticalAlignment = VerticalAlignment.Center, Foreground = glyphBrush ?? (Brush)res["TextBrush"],
};
DockPanel.SetDock(icon, Dock.Left);
content.Children.Add(icon);
var texts = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
texts.Children.Add(new TextBlock { Text = text, Foreground = (Brush)res["TextBrush"] });
if (hint is not null)
texts.Children.Add(new TextBlock { Text = hint, FontSize = 11, Foreground = (Brush)res["TextTertiaryBrush"] });
content.Children.Add(texts);
var b = new Button
{
Style = (Style)res["SubtleButton"], Content = content, HorizontalContentAlignment = HorizontalAlignment.Stretch,
Padding = new Thickness(10, 7, 10, 7), Focusable = true,
};
b.Click += (_, _) => click();
return b;
}
private static Border Divider() => new()
{
Height = 1, Margin = new Thickness(4, 4, 4, 4), Background = (Brush)Application.Current.Resources["ControlStrokeBrush"],
};
private void Pick(FenceTab tab) => Finish(tab);
/// <summary>Für Auswahlen mit eigenem Dialog: erst die Auswahl schließen, Rahmen aber stehen lassen.</summary>
private void PickLater(Func<FenceTab?> ask)
{
_done = true;
Hide();
var tab = ask();
if (tab is not null) _create(tab);
Close();
}
private void Finish(FenceTab? tab)
{
if (_done) return;
_done = true;
Close();
if (tab is not null) _create(tab);
}
/// <summary>
/// DeskCore läuft im Hintergrund – Windows lässt es nicht ohne Weiteres nach vorne. Ein kurzer Alt-Tastendruck
/// gibt die Erlaubnis (bewährter Weg, der auch von Windows selbst so akzeptiert wird).
/// </summary>
private void BringToFront()
{
var hwnd = new WindowInteropHelper(this).Handle;
if (GetForegroundWindow() != hwnd)
{
// Loslassen erst nach dem Wechsel – so landet es bei uns und öffnet nirgends ein Menü
keybd_event(VK_MENU, 0, 0, UIntPtr.Zero);
SetForegroundWindow(hwnd);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
}
Activate();
Focus();
}
private const byte VK_MENU = 0x12;
private const uint KEYEVENTF_KEYUP = 0x2;
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p);
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")] private static extern void keybd_event(byte vk, byte scan, uint flags, UIntPtr extra);
}
+3 -2
View File
@@ -81,10 +81,11 @@ public static class DesktopIcons
return changed;
}
public static bool HitsIcon(int x, int y) => Cached.Any(i =>
/// <param name="margin">Zusätzlicher Rand in Pixeln, damit Klicks knapp neben Bild oder Beschriftung auch zählen.</param>
public static bool HitsIcon(int x, int y, int margin = 0) => Cached.Any(i =>
{
var (l, t, w, h) = CellRect(i);
return x >= l && x < l + w && y >= t && y < t + h;
return x >= l - margin && x < l + w + margin && y >= t - margin && y < t + h + margin;
});
// ---------- Desktop-Ansicht finden ----------
+4 -2
View File
@@ -172,12 +172,14 @@ public static class DrawBoxOverlay
public static void Finish(Rect px)
{
_w?.Hide();
double s = Screens.Scale;
var dip = new Rect(px.X / s, px.Y / s, px.Width / s, px.Height / s);
if (dip.Width < 120 || dip.Height < 60) return; // zu klein – war wohl nur ein Versehen
if (dip.Width < 120 || dip.Height < 60) { Hide(); return; } // zu klein – war wohl nur ein Versehen
Update(px); // Rahmen bleibt stehen, bis etwas ausgewählt ist
FenceManager.ShowCreateMenu(dip);
}
public static void Hide() => _w?.Hide();
}
// =========================================================================
+12 -3
View File
@@ -68,7 +68,10 @@ public static class DesktopToggle
{
_lastDownTick = 0;
var pt = info.pt;
dispatcher?.BeginInvoke(() => OnDoubleClick(pt));
var first = _lastDownPt;
// Schon im Hook (ohne COM) aussortieren: Doppelklick auf ein Icon = Programmstart
if (!DesktopIcons.HitsIcon(pt.x, pt.y, margin: 8) && !DesktopIcons.HitsIcon(first.x, first.y, margin: 8))
dispatcher?.BeginInvoke(() => OnDoubleClick(pt, first));
}
else
{
@@ -144,9 +147,15 @@ public static class DesktopToggle
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
}
private static void OnDoubleClick(POINT pt)
private static void OnDoubleClick(POINT pt, POINT first)
{
if (!IsEmptyDesktopSpot(pt)) return;
if (!IsEmptyDesktopSpot(pt) || !IsEmptyDesktopSpot(first)) return;
// Positionen frisch lesen – der Zwischenspeicher kann ein paar Sekunden alt sein (Icon gerade verschoben)
if (IconsVisible())
{
try { DesktopIcons.RefreshCache(); } catch { return; }
if (DesktopIcons.HitsIcon(pt.x, pt.y, margin: 8) || DesktopIcons.HitsIcon(first.x, first.y, margin: 8)) return;
}
Toggle();
}
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<!-- Eine Versionsnummer für App und Installer – nur hier ändern -->
<PropertyGroup>
<Version>1.0.2</Version>
<Version>1.0.4</Version>
<!-- keine Git-Kennung an die Produktversion anhängen (sonst "1.0.1+a1b2c3…") -->
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
+50 -41
View File
@@ -73,14 +73,30 @@ public static class FenceManager
App.Win?.RefreshFromFences();
}
/// <summary>Kategorie aus allen Boxen (außer except) entfernen – eine Kategorie soll nur in einer Box stehen.</summary>
public static void RemoveCategoryTab(string category, FenceDef? except)
/// <summary>
/// Kategorie aus den anderen Boxen derselben Seite entfernen – pro Seite steht eine Kategorie nur in einer Box.
/// Auf anderen Seiten darf sie zusätzlich liegen (z. B. „Internet“ auf „Arbeit“ und auf „Freizeit“).
/// </summary>
public static void RemoveCategoryTab(string category, FenceDef? except, int page)
{
foreach (var f in S.Fences.Where(f => f != except))
f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
foreach (var f in S.Fences.Where(f => f != except && f.Page == page))
f.Tabs.RemoveAll(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
S.Fences.RemoveAll(f => f != except && f.Tabs.Count == 0);
}
/// <summary>Nach Verschieben/Zusammenlegen: doppelte Kategorien auf einer Seite auflösen – „keep“ behält seine.</summary>
private static void ResolveDuplicates(int page, FenceDef? keep)
{
if (keep is not null)
foreach (var t in keep.Tabs.Where(t => t.IsCategory).ToList())
RemoveCategoryTab(t.Value, keep, page);
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var f in S.Fences.Where(f => f.Page == page))
f.Tabs.RemoveAll(t => t.IsCategory && !seen.Add(t.Value));
S.Fences.RemoveAll(f => f.Tabs.Count == 0);
foreach (var f in S.Fences) f.ActiveTab = Math.Clamp(f.ActiveTab, 0, Math.Max(0, f.Tabs.Count - 1));
}
/// <summary>Tab als eigene Box daneben lösen.</summary>
public static void DetachTab(FenceDef def, int index)
{
@@ -117,18 +133,12 @@ public static class FenceManager
public static List<Rect> OpenRects() =>
_open.Where(w => w.IsVisible).Select(w => new Rect(w.Left, w.Top, w.ActualWidth, w.ActualHeight)).ToList();
/// <summary>Nach dem Aufziehen: Menü "Neue Box hier" an der Mausposition.</summary>
/// <summary>Nach dem Aufziehen: Auswahl "Neue Box hier" an der Mausposition.</summary>
public static void ShowCreateMenu(Rect area)
{
var menu = new ContextMenu { Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint };
var header = MainWindow.MenuItem("Neue Box hier für …", "\uE8A1", null, bold: true);
header.IsEnabled = false;
menu.Items.Add(header);
menu.Items.Add(new Separator());
void Create(FenceTab tab)
BoxPicker.Show(area, tab =>
{
if (!tab.IsFolder) RemoveCategoryTab(tab.Value, except: null);
if (tab.IsCategory) RemoveCategoryTab(tab.Value, except: null, S.CurrentPage);
S.Fences.Add(new FenceDef
{
Page = S.CurrentPage, Tabs = [tab],
@@ -138,35 +148,9 @@ public static class FenceManager
S.Save();
Sync();
App.Win?.RefreshFromFences();
});
}
foreach (var c in Storage.Categories())
{
var label = S.HasFence(c) ? $"{c} (aus anderer Box hierher holen)" : c;
menu.Items.Add(MainWindow.MenuItem(label, S.Categories.GetValueOrDefault(c)?.Glyph ?? "\uF12B",
() => Create(new FenceTab { Value = c })));
}
if (Storage.Categories().Count > 0) menu.Items.Add(new Separator());
menu.Items.Add(MainWindow.MenuItem("Neue Kategorie …", "\uE710", () =>
{
if (App.Win?.CreateCategoryInteractive() is { } name) Create(new FenceTab { Value = name });
}));
menu.Items.Add(MainWindow.MenuItem("Ordner-Portal …", "\uF12B", () =>
{
if (PickFolder() is { } folder) Create(new FenceTab { Kind = FenceTab.FolderKind, Value = folder });
}));
menu.Items.Add(new Separator());
menu.Items.Add(MainWindow.MenuItem("Abbrechen", "\uE711", () => { }));
// Damit das Menü bei Klick daneben zugeht, muss es im Vordergrund sein
menu.Opened += (_, _) =>
{
if (PresentationSource.FromVisual(menu) is System.Windows.Interop.HwndSource src) SetForegroundWindow(src.Handle);
};
menu.IsOpen = true;
}
[System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
// =====================================================================
// Desktop-Seiten
// =====================================================================
@@ -186,6 +170,7 @@ public static class FenceManager
S.CurrentPage = index;
S.Save();
if (changed) Sync();
PageScheduler.ApplyWallpaper(index);
PageIndicator.Show(S.Pages[index], index, S.Pages.Count);
App.Win?.RefreshFromFences();
}
@@ -193,8 +178,24 @@ public static class FenceManager
public static void MoveToPage(FenceDef def, int page)
{
def.Page = Math.Clamp(page, 0, S.Pages.Count - 1);
ResolveDuplicates(def.Page, keep: def);
S.Save();
Sync();
App.Win?.RefreshFromFences();
}
/// <summary>Box mit gleichem Inhalt und Aussehen zusätzlich auf eine andere Seite legen.</summary>
public static void CopyToPage(FenceDef def, int page)
{
var copy = AppSettings.CloneFences([def])[0];
copy.Id = Guid.NewGuid().ToString("N");
copy.Page = Math.Clamp(page, 0, S.Pages.Count - 1);
S.Fences.Add(copy);
ResolveDuplicates(copy.Page, keep: copy);
S.Save();
Sync();
App.Win?.RefreshFromFences();
App.TrayIcon?.Notify("Box kopiert", $"Die Box liegt jetzt auch auf „{S.Pages[copy.Page]}“ – an derselben Stelle.");
}
/// <summary>Seite löschen: ihre Boxen wandern auf die Seite davor.</summary>
@@ -205,6 +206,8 @@ public static class FenceManager
foreach (var f in S.Fences)
if (f.Page == index) f.Page = Math.Max(0, index - 1);
else if (f.Page > index) f.Page--;
// Kategorien, die es auf beiden Seiten gab, nicht doppelt zeigen
ResolveDuplicates(Math.Max(0, index - 1), keep: null);
if (S.CurrentPage >= index && S.CurrentPage > 0) S.CurrentPage--;
S.Save();
CloseAll();
@@ -267,10 +270,16 @@ public static class FenceManager
v => def.IconSize = Enum.Parse<ViewMode>(v)),
});
panel.Children.Add(Label("Sortieren nach"));
panel.Children.Add(new TextBlock
{
Text = "„Eigene“: Icons in der Box einfach an die gewünschte Stelle ziehen.", FontSize = 12, Margin = new Thickness(0, -4, 0, 8),
TextWrapping = TextWrapping.Wrap, Foreground = (Brush)res["TextSecondaryBrush"],
});
panel.Children.Add(new Border
{
Style = (Style)res["SegmentGroup"], HorizontalAlignment = HorizontalAlignment.Left,
Child = Segments("sort", [("Name", "Name"), ("Typ", "Typ"), ("Datum", "Datum")], def.Sort, v => def.Sort = v),
Child = Segments("sort", [("Name", "Name"), ("Typ", "Typ"), ("Datum", "Datum"), ("Meistgenutzt", "Häufig"), ("Eigene", "Eigene")],
def.Sort, v => def.Sort = v),
});
// Icon-Tönung
+6 -2
View File
@@ -53,7 +53,7 @@
<Grid x:Name="Body">
<ListBox x:Name="Items" Padding="6,0,6,8" SelectionMode="Extended" ItemContainerStyle="{StaticResource TileItem}"
AllowDrop="True" DragOver="Items_DragOver" Drop="Items_Drop"
AllowDrop="True" DragOver="Items_DragOver" DragLeave="Items_DragLeave" Drop="Items_Drop"
MouseDoubleClick="Items_MouseDoubleClick" KeyDown="Items_KeyDown"
PreviewMouseLeftButtonDown="Items_PreviewMouseLeftButtonDown" PreviewMouseMove="Items_PreviewMouseMove"
ContextMenuOpening="Items_ContextMenuOpening">
@@ -84,7 +84,11 @@
</ListBox.ItemTemplate>
</ListBox>
<TextBlock x:Name="EmptyText" Text="Leer – Icons hierher ziehen" HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource TextTertiaryBrush}" IsHitTestVisible="False" Margin="0,0,0,10"/>
Foreground="{DynamicResource TextTertiaryBrush}" IsHitTestVisible="False" Margin="16,0,16,10"
TextWrapping="Wrap" TextAlignment="Center"/>
<!-- Einfügestelle beim Umsortieren per Ziehen -->
<Rectangle x:Name="DropMarker" Width="3" RadiusX="1.5" RadiusY="1.5" Fill="{DynamicResource AccentBrush}"
HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False" Visibility="Collapsed"/>
</Grid>
</DockPanel>
</Border>
+117 -19
View File
@@ -55,7 +55,8 @@ public partial class FenceWindow : Window
SourceInitialized += (_, _) => PinToDesktop();
MouseEnter += (_, _) => OnHover(true);
MouseLeave += (_, _) => OnHover(false);
Closed += (_, _) => _portalWatcher?.Dispose();
Usage.Changed += OnUsageChanged;
Closed += (_, _) => { _portalWatcher?.Dispose(); Usage.Changed -= OnUsageChanged; };
Refresh();
}
@@ -215,29 +216,52 @@ public partial class FenceWindow : Window
WatchPortal(portal ? folder : null);
List<IconItem> items = [];
if (folder is not null && Directory.Exists(folder))
if (tab?.IsRecent == true)
items = Usage.Recent(RecentCount).Select(p => new IconItem { Path = p }).ToList();
else if (folder is not null && Directory.Exists(folder))
items = (portal ? PortalEntries(folder) : Storage.Entries(folder)).Select(p => new IconItem { Path = p }).ToList();
items = Sort(items);
items = Sort(items, tab);
int px = (int)Math.Round((double)Resources["FIcon"] * VisualTreeHelper.GetDpi(this).DpiScaleX);
foreach (var i in items) IconLoader.Load(i, px);
Items.ItemsSource = items;
CountText.Text = items.Count.ToString();
EmptyText.Text = folder is not null && !Directory.Exists(folder) ? "Ordner nicht gefunden" : "Leer – Icons hierher ziehen";
EmptyText.Text = tab?.IsRecent == true ? "Noch nichts gestartet – was du über DeskCore öffnest, erscheint hier"
: folder is not null && !Directory.Exists(folder) ? "Ordner nicht gefunden" : "Leer – Icons hierher ziehen";
EmptyText.Visibility = items.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>Ordner-Portal zeigt alles außer versteckten/System-Dateien.</summary>
private static IEnumerable<string> PortalEntries(string dir) => Storage.Entries(dir);
private List<IconItem> Sort(List<IconItem> items)
/// <summary>Wie viele Einträge eine „Zuletzt benutzt“-Box zeigt.</summary>
private const int RecentCount = 24;
private void OnUsageChanged()
{
// Zuletzt-Tab oder Sortierung nach Häufigkeit → neu ordnen (kurz verzögert, falls mehrere Starts kommen)
if (Tab?.IsRecent == true || Def.Sort == "Häufig") Dispatcher.BeginInvoke(Refresh, DispatcherPriority.Background);
}
private List<IconItem> Sort(List<IconItem> items, FenceTab? tab)
{
// Zuletzt benutzt: neueste zuerst – außer es ist ausdrücklich "Häufig" gewählt
if (tab?.IsRecent == true)
return Def.Sort == "Häufig" ? items.OrderByDescending(i => Usage.Score(i.Path)).ToList() : items;
// Eigene Reihenfolge: wie gezogen; Neues hinten dran (alphabetisch)
if (Def.Sort == "Eigene" && tab is not null)
{
var order = tab.Order;
return items.OrderBy(i => order.FindIndex(n => string.Equals(n, Path.GetFileName(i.Path), StringComparison.OrdinalIgnoreCase)) is var k and >= 0 ? k : int.MaxValue)
.ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
}
var folders = items.Where(i => Directory.Exists(i.Path));
var files = items.Where(i => !Directory.Exists(i.Path));
IEnumerable<IconItem> Order(IEnumerable<IconItem> x) => Def.Sort switch
{
"Typ" => x.OrderBy(i => Path.GetExtension(i.Path), StringComparer.OrdinalIgnoreCase).ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase),
"Datum" => x.OrderByDescending(i => { try { return File.GetLastWriteTime(i.Path); } catch { return DateTime.MinValue; } }),
"Häufig" => x.OrderByDescending(i => Usage.Score(i.Path)).ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase),
_ => x.OrderBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase),
};
return Order(folders).Concat(Order(files)).ToList();
@@ -250,13 +274,13 @@ public partial class FenceWindow : Window
{
var t = Def.Tabs[i];
bool active = i == Math.Clamp(Def.ActiveTab, 0, Def.Tabs.Count - 1);
var meta = t.IsFolder ? null : S.Categories.GetValueOrDefault(t.Value);
var meta = t.IsCategory ? S.Categories.GetValueOrDefault(t.Value) : null;
var glyph = new TextBlock
{
Text = t.IsFolder ? "" : meta?.Glyph ?? "",
Text = t.IsRecent ? "" : t.IsFolder ? "" : meta?.Glyph ?? "",
FontFamily = (FontFamily)FindResource("IconFont"), FontSize = 13, VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 7, 0),
Foreground = CategoryColors.Brush(meta?.Color) ?? (Brush)FindResource(t.IsFolder ? "AccentBrush" : "TextBrush"),
Foreground = CategoryColors.Brush(meta?.Color) ?? (Brush)FindResource(t.IsCategory ? "TextBrush" : "AccentBrush"),
};
var label = new TextBlock
{
@@ -275,7 +299,7 @@ public partial class FenceWindow : Window
{
Style = (Style)FindResource("SubtleButton"), Padding = new Thickness(6, 4, 6, 4), MinHeight = 0, Tag = i,
Content = new Grid { Children = { content, underline } }, Margin = new Thickness(0, 0, 2, 0),
ToolTip = t.IsFolder ? t.Value : $"Kategorie „{t.Value}“",
ToolTip = t.IsRecent ? "Was du zuletzt über DeskCore gestartet hast" : t.IsFolder ? t.Value : $"Kategorie „{t.Value}“",
};
WindowChrome.SetIsHitTestVisibleInChrome(btn, Def.Tabs.Count > 1); // bei nur einem Tab: Titel = Ziehfläche
btn.Click += (_, _) => SwitchTab((int)btn.Tag);
@@ -344,7 +368,7 @@ public partial class FenceWindow : Window
Refresh();
return;
}
try { Process.Start(new ProcessStartInfo(i.Path) { UseShellExecute = true }); }
try { Usage.Start(i.Path); }
catch { }
}
@@ -383,7 +407,7 @@ public partial class FenceWindow : Window
bool inStorage = sel.All(i => Storage.IsInStorage(i.Path));
var moveTo = MainWindow.MenuItem(inStorage ? "Verschieben nach" : "Sichern in", inStorage ? "" : "", null);
var ownCategory = Tab is { IsFolder: false } t ? t.Value : null;
var ownCategory = Tab is { IsCategory: true } t ? t.Value : null;
foreach (var c in Storage.Categories().Where(c => !string.Equals(c, ownCategory, StringComparison.OrdinalIgnoreCase)))
moveTo.Items.Add(MainWindow.MenuItem(c, S.Categories.GetValueOrDefault(c)?.Glyph ?? "",
() => win.TransferAll(sel.Select(i => i.Path), Storage.CategoryPath(c))));
@@ -423,18 +447,85 @@ public partial class FenceWindow : Window
private void Items_DragOver(object sender, DragEventArgs e)
{
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) && !_dragging && CurrentFolder is not null
if (_dragging)
{
// Innerhalb der eigenen Box: neu anordnen (nicht bei „Zuletzt benutzt“ – die ordnet sich selbst)
e.Effects = Tab is { IsRecent: false } ? DragDropEffects.Copy : DragDropEffects.None;
ShowDropMarker(e.Effects == DragDropEffects.None ? null : e.GetPosition(Items));
}
else
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) && CurrentFolder is not null
? (e.AllowedEffects.HasFlag(DragDropEffects.Copy) ? DragDropEffects.Copy : DragDropEffects.Move)
: DragDropEffects.None;
e.Handled = true;
}
private void Items_DragLeave(object sender, DragEventArgs e) => ShowDropMarker(null);
private void Items_Drop(object sender, DragEventArgs e)
{
if (!_dragging && CurrentFolder is { } folder && e.Data.GetData(DataFormats.FileDrop) is string[] files)
ShowDropMarker(null);
if (_dragging)
{
if (Tab is { IsRecent: false } t) Reorder(t, Selected, e.GetPosition(Items));
return;
}
if (CurrentFolder is { } folder && e.Data.GetData(DataFormats.FileDrop) is string[] files)
App.Win?.TransferAll(files, folder);
}
// ---------- Eigene Reihenfolge ----------
/// <summary>Einfügestelle an der Mausposition: vor welchem Eintrag landet das Gezogene? (Count = ans Ende)</summary>
private (int Index, Rect? Marker) InsertionAt(Point p)
{
var list = Items.ItemsSource as List<IconItem> ?? [];
for (int k = 0; k < list.Count; k++)
{
if (Items.ItemContainerGenerator.ContainerFromIndex(k) is not ListBoxItem c || !c.IsVisible) continue;
var r = new Rect(c.TranslatePoint(new Point(0, 0), Items), c.RenderSize);
if (p.Y < r.Top) return (k, new Rect(r.Left - 2, r.Top, 3, r.Height)); // Zeile darüber, ganz links
if (p.Y <= r.Bottom && p.X < r.Left + r.Width / 2) return (k, new Rect(r.Left - 2, r.Top, 3, r.Height));
}
if (list.Count > 0 && Items.ItemContainerGenerator.ContainerFromIndex(list.Count - 1) is ListBoxItem last)
{
var r = new Rect(last.TranslatePoint(new Point(0, 0), Items), last.RenderSize);
return (list.Count, new Rect(r.Right - 1, r.Top, 3, r.Height));
}
return (list.Count, null);
}
private void ShowDropMarker(Point? p)
{
if (p is { } pos && InsertionAt(pos).Marker is { } m)
{
DropMarker.Margin = new Thickness(m.Left, m.Top, 0, 0);
DropMarker.Height = m.Height;
DropMarker.Visibility = Visibility.Visible;
}
else DropMarker.Visibility = Visibility.Collapsed;
}
private void Reorder(FenceTab tab, List<IconItem> moved, Point drop)
{
var list = Items.ItemsSource as List<IconItem> ?? [];
if (moved.Count == 0) return;
int index = InsertionAt(drop).Index;
// Einfügestelle in der Liste ohne die gezogenen Einträge
int before = list.Take(index).Count(i => moved.Contains(i));
var rest = list.Where(i => !moved.Contains(i)).ToList();
rest.InsertRange(Math.Clamp(index - before, 0, rest.Count), moved);
tab.Order = rest.Select(i => Path.GetFileName(i.Path)).ToList();
bool switched = Def.Sort != "Eigene";
Def.Sort = "Eigene";
S.Save();
Refresh();
foreach (var i in (Items.ItemsSource as List<IconItem> ?? []).Where(i => moved.Any(m => m.Path == i.Path)))
Items.SelectedItems.Add(i);
if (switched) App.TrayIcon?.Notify("Eigene Reihenfolge", "Die Box ist jetzt so sortiert, wie du die Icons ziehst. Ändern unter „Aussehen …“.");
}
// =====================================================================
// Menüs
// =====================================================================
@@ -448,15 +539,17 @@ public partial class FenceWindow : Window
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
FenceMenu.Items.Clear();
if (Tab is { IsFolder: false } t)
if (Tab is { IsCategory: true } t)
FenceMenu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", () => App.Win?.ShowCategory(t.Value)));
else if (Tab is { IsFolder: true } pt)
FenceMenu.Items.Add(MainWindow.MenuItem("Im Explorer öffnen", "", () => Process.Start("explorer.exe", $"\"{CurrentFolder ?? pt.Value}\"")));
// Tabs
var add = MainWindow.MenuItem("Tab hinzufügen", "", null);
foreach (var c in Storage.Categories().Where(c => !Def.Tabs.Any(x => !x.IsFolder && string.Equals(x.Value, c, StringComparison.OrdinalIgnoreCase))))
foreach (var c in Storage.Categories().Where(c => !Def.Tabs.Any(x => x.IsCategory && string.Equals(x.Value, c, StringComparison.OrdinalIgnoreCase))))
add.Items.Add(MainWindow.MenuItem(c, S.Categories.GetValueOrDefault(c)?.Glyph ?? "", () => AddTab(new FenceTab { Value = c })));
if (!Def.Tabs.Any(x => x.IsRecent))
add.Items.Add(MainWindow.MenuItem("Zuletzt benutzt", "", () => AddTab(new FenceTab { Kind = FenceTab.RecentKind })));
if (add.Items.Count > 0) add.Items.Add(new Separator());
add.Items.Add(MainWindow.MenuItem("Ordner-Portal…", "", () =>
{
@@ -475,14 +568,19 @@ public partial class FenceWindow : Window
if (S.Pages.Count > 1)
{
var move = MainWindow.MenuItem("Auf Seite verschieben", "\uE8AB", null);
var copy = MainWindow.MenuItem("Auf Seite kopieren", "\uE8C8", null);
for (int p = 0; p < S.Pages.Count; p++)
{
int page = p;
var mi = MainWindow.MenuItem(S.Pages[p], "\uE8A1", () => FenceManager.MoveToPage(Def, page));
mi.IsEnabled = page != Def.Page;
move.Items.Add(mi);
var ci = MainWindow.MenuItem(S.Pages[p], "\uE8A1", () => FenceManager.CopyToPage(Def, page));
ci.IsEnabled = page != Def.Page;
copy.Items.Add(ci);
}
FenceMenu.Items.Add(move);
FenceMenu.Items.Add(copy);
}
FenceMenu.Items.Add(new Separator());
FenceMenu.Items.Add(MainWindow.MenuItem("Box schließen", "", () => FenceManager.Remove(Def)));
@@ -495,8 +593,8 @@ public partial class FenceWindow : Window
if (index < 0 || index >= Def.Tabs.Count) return;
var t = Def.Tabs[index];
var menu = new ContextMenu();
if (!t.IsFolder) menu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", () => App.Win?.ShowCategory(t.Value)));
else menu.Items.Add(MainWindow.MenuItem("Im Explorer öffnen", "", () => Process.Start("explorer.exe", $"\"{t.Value}\"")));
if (t.IsCategory) menu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", () => App.Win?.ShowCategory(t.Value)));
else if (t.IsFolder) menu.Items.Add(MainWindow.MenuItem("Im Explorer öffnen", "", () => Process.Start("explorer.exe", $"\"{t.Value}\"")));
if (Def.Tabs.Count > 1)
{
menu.Items.Add(MainWindow.MenuItem("Als eigene Box lösen", "", () => FenceManager.DetachTab(Def, index)));
@@ -507,8 +605,8 @@ public partial class FenceWindow : Window
private void AddTab(FenceTab tab)
{
// Kategorie steckt schon in einer anderen Box? Dann dorthin verschieben statt doppelt anzeigen
if (!tab.IsFolder) FenceManager.RemoveCategoryTab(tab.Value, except: Def);
// Kategorie steckt auf dieser Seite schon in einer anderen Box? Dann hierher holen statt doppelt anzeigen
if (tab.IsCategory) FenceManager.RemoveCategoryTab(tab.Value, except: Def, Def.Page);
Def.Tabs.Add(tab);
Def.ActiveTab = Def.Tabs.Count - 1;
_portalSub = null;
+6 -1
View File
@@ -234,11 +234,16 @@ public partial class MainWindow
{
var setup = await RunWithProgress($"Update auf {info.Version}", "Neue Version wird heruntergeladen …",
(sink, ct) => UpdateChecker.DownloadSetupAsync(info, (done, total) => { sink.SetTotal(total); sink.SetDone(done); }, ct));
// Installer still ausführen und DeskCore danach automatisch neu starten
// Installer still ausführen und DeskCore danach automatisch neu starten.
// Windows fragt dabei nach Administratorrechten (der Installer kümmert sich auch um die .NET-Laufzeit).
Process.Start(new ProcessStartInfo(setup, "/S /launch") { UseShellExecute = true });
App.Quit();
}
catch (OperationCanceledException) { ShowToast("Update abgebrochen", "", info: true); }
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
ShowToast("Update abgebrochen – bitte die Windows-Abfrage mit „Ja“ bestätigen", "", info: true);
}
catch (Exception ex) { Error("Update fehlgeschlagen", $"{ex.Message}\n\nDu kannst die neue Version auch selbst herunterladen:\n{info.PageUrl}"); }
}
}
+3 -1
View File
@@ -62,6 +62,7 @@ public partial class MainWindow
ShowToast($"„{g.Name}“ ist noch leer", "", info: true);
return;
}
Usage.Record(Usage.GroupPrefix + g.Name); // damit die Schnellstart-Suche häufige Gruppen weiter oben zeigt
int started = 0;
var problems = new List<string>();
var items = g.Items.ToList();
@@ -72,7 +73,7 @@ public partial class MainWindow
if (!File.Exists(p) && !Directory.Exists(p)) { problems.Add($"„{Storage.DisplayName(p)}“ gibt es nicht mehr"); continue; }
try
{
Process.Start(new ProcessStartInfo(p) { UseShellExecute = true });
Usage.Start(p);
started++;
}
catch (Exception ex) { problems.Add($"„{Storage.DisplayName(p)}“: {ex.Message}"); }
@@ -206,6 +207,7 @@ public partial class MainWindow
if (form is null) return;
var before = (g.Name, g.DelaySeconds, g.Color);
(g.Name, g.DelaySeconds, g.Color) = form.Value;
Usage.Rename(Usage.GroupPrefix + before.Name, Usage.GroupPrefix + g.Name);
S.Save();
var entry = new UndoEntry($"Start-Gruppe „{before.Name}“ bearbeiten");
entry.Add(() => { (g.Name, g.DelaySeconds, g.Color) = before; S.Save(); return null; });
+132 -2
View File
@@ -1,3 +1,4 @@
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -23,6 +24,10 @@ public partial class MainWindow
{
int boxes = S.Fences.Count(f => f.Page == i);
var info = (boxes == 1 ? "1 Box" : $"{boxes} Boxen") + (i == S.CurrentPage ? " · wird gerade angezeigt" : "");
var o = S.PageOptions.GetValueOrDefault(name);
if (o?.Scheduled == true) info += " · " + PageScheduler.Describe(o);
if (string.Equals(S.PageFallback, name, StringComparison.OrdinalIgnoreCase)) info += " · sonst immer";
if (o?.Wallpaper is not null) info += " · eigenes Hintergrundbild";
return new PageRow(i, name, info, (Brush)res[i == S.CurrentPage ? "AccentBrush" : "ControlStrongStrokeBrush"]);
}).ToList();
@@ -46,7 +51,7 @@ public partial class MainWindow
DesktopTintSwatches.Children.Add(rb);
}
AddSwatch(null, Brushes.Transparent, "Keine Tönung");
foreach (var (name, hex) in CategoryColors.All.Concat([("Grau", "#8A8A8A"), ("Weiß", "#F5F5F5")]))
foreach (var (name, hex) in CategoryColors.All.Concat([("Weiß", "#F5F5F5")]))
AddSwatch(hex, CategoryColors.Brush(hex)!, name);
DesktopTintStrength.Value = Math.Round(S.DesktopTintStrength * 100);
DesktopTintStrength.IsEnabled = S.DesktopTint is not null;
@@ -106,11 +111,134 @@ public partial class MainWindow
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
var name = Dialog.Prompt(this, "Seite umbenennen", "Neuer Name:", S.Pages[i], "Umbenennen", t => ValidatePageName(t, i));
if (name is null) return;
S.Pages[i] = name.Trim();
S.RenamePage(i, name.Trim());
S.Save();
LoadDesktopSettings();
}
// ---------- Zeitplan & Hintergrundbild ----------
private void PageSchedule_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
var page = S.Pages[i];
var o = S.PageOption(page);
var res = Application.Current.Resources;
var panel = new StackPanel();
TextBlock Label(string t, double top = 18) => new()
{
Text = t, Margin = new Thickness(0, top, 0, 8), FontWeight = FontWeights.SemiBold, FontSize = 13,
};
TextBlock Hint(string t) => new()
{
Text = t, FontSize = 12, TextWrapping = TextWrapping.Wrap, Foreground = (Brush)res["TextSecondaryBrush"],
Margin = new Thickness(0, 4, 0, 0),
};
// Zeitplan
var scheduled = new CheckBox { Content = "Diese Seite automatisch anzeigen", IsChecked = o.Scheduled };
panel.Children.Add(scheduled);
var details = new StackPanel { Margin = new Thickness(28, 0, 0, 0) };
details.Children.Add(Label("An diesen Tagen", 12));
var days = new WrapPanel();
string[] dayNames = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
var dayBoxes = new List<CheckBox>();
for (int d = 0; d < 7; d++)
{
var cb = new CheckBox { Content = dayNames[d], IsChecked = (o.Days & (1 << d)) != 0, Margin = new Thickness(0, 0, 14, 4) };
dayBoxes.Add(cb);
days.Children.Add(cb);
}
details.Children.Add(days);
details.Children.Add(Label("Von – bis"));
var from = new TextBox { Text = o.From, Width = 76, HorizontalContentAlignment = HorizontalAlignment.Center };
var to = new TextBox { Text = o.To, Width = 76, HorizontalContentAlignment = HorizontalAlignment.Center };
details.Children.Add(new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
from,
new TextBlock { Text = "bis", Margin = new Thickness(12, 0, 12, 0), VerticalAlignment = VerticalAlignment.Center },
to,
new TextBlock { Text = "Uhr", Margin = new Thickness(12, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center },
},
});
details.Children.Add(Hint("Über Mitternacht geht auch, z. B. 22:00 bis 06:00."));
panel.Children.Add(details);
void UpdateEnabled() => details.IsEnabled = scheduled.IsChecked == true;
scheduled.Click += (_, _) => UpdateEnabled();
UpdateEnabled();
var fallback = new CheckBox
{
Content = "Zu allen anderen Zeiten diese Seite anzeigen", Margin = new Thickness(0, 18, 0, 0),
IsChecked = string.Equals(S.PageFallback, page, StringComparison.OrdinalIgnoreCase),
};
panel.Children.Add(fallback);
panel.Children.Add(Hint("Beispiel: „Arbeit“ Mo–Fr 08:00–17:00 und bei „Freizeit“ diesen Haken setzen."));
// Hintergrundbild
panel.Children.Add(Label("Hintergrundbild", 22));
string? wallpaper = o.Wallpaper;
var wpText = new TextBlock { TextTrimming = TextTrimming.CharacterEllipsis, VerticalAlignment = VerticalAlignment.Center };
var wpRemove = new Button { Content = "Entfernen", Margin = new Thickness(8, 0, 0, 0) };
void UpdateWallpaper()
{
wpText.Text = wallpaper is null ? "Keins – der Hintergrund bleibt, wie er ist" : Path.GetFileName(wallpaper);
wpText.ToolTip = wallpaper;
wpRemove.IsEnabled = wallpaper is not null;
}
var wpPick = new Button { Content = "Bild wählen …" };
wpPick.Click += (_, _) =>
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
Title = $"Hintergrundbild für „{page}“", Filter = "Bilder|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp|Alle Dateien|*.*",
};
if (wallpaper is not null) dlg.InitialDirectory = Path.GetDirectoryName(wallpaper);
if (dlg.ShowDialog() == true) { wallpaper = dlg.FileName; UpdateWallpaper(); }
};
wpRemove.Click += (_, _) => { wallpaper = null; UpdateWallpaper(); };
UpdateWallpaper();
var wpRow = new DockPanel();
var wpButtons = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(12, 0, 0, 0), Children = { wpPick, wpRemove } };
DockPanel.SetDock(wpButtons, Dock.Right);
wpRow.Children.Add(wpButtons);
wpRow.Children.Add(wpText);
panel.Children.Add(wpRow);
panel.Children.Add(Hint("Wechselst du auf eine Seite ohne eigenes Bild, kommt dein ursprünglicher Hintergrund zurück."));
string? Validate()
{
if (scheduled.IsChecked != true) return null;
if (!TimeSpan.TryParse(from.Text.Trim(), out var f) || f.TotalHours >= 24 ||
!TimeSpan.TryParse(to.Text.Trim(), out var t) || t.TotalHours >= 24)
return "Bitte die Uhrzeiten so eingeben: 08:00";
if (f == t) return "Start und Ende dürfen nicht gleich sein.";
if (!dayBoxes.Any(b => b.IsChecked == true)) return "Bitte mindestens einen Tag auswählen.";
return null;
}
if (!Dialog.Show(this, $"Seite „{page}“", panel, "Speichern", "Abbrechen", Validate)) return;
o.Scheduled = scheduled.IsChecked == true;
o.Days = dayBoxes.Select((b, d) => b.IsChecked == true ? 1 << d : 0).Sum();
if (TimeSpan.TryParse(from.Text.Trim(), out var ff)) o.From = ff.ToString(@"hh\:mm");
if (TimeSpan.TryParse(to.Text.Trim(), out var tt)) o.To = tt.ToString(@"hh\:mm");
if (fallback.IsChecked == true) S.PageFallback = page;
else if (string.Equals(S.PageFallback, page, StringComparison.OrdinalIgnoreCase)) S.PageFallback = null;
bool wallpaperChanged = !string.Equals(o.Wallpaper, wallpaper, StringComparison.OrdinalIgnoreCase);
o.Wallpaper = wallpaper;
S.Save();
if (wallpaperChanged && i == S.CurrentPage) PageScheduler.ApplyWallpaper(i);
PageScheduler.Reset();
PageScheduler.Tick();
LoadDesktopSettings();
ShowToast($"Seite „{page}“ gespeichert", "");
}
private void DeletePage_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
@@ -126,6 +254,8 @@ public partial class MainWindow
"Löschen"))
return;
FenceManager.DeletePage(i);
S.ForgetPage(name);
S.Save();
DesktopToggle.Apply();
Refresh();
LoadDesktopSettings();
+58
View File
@@ -0,0 +1,58 @@
using System.Windows;
using System.Windows.Input;
namespace DeskCore;
/// <summary>Einstellungen der Schnellstart-Suche.</summary>
public partial class MainWindow
{
private void LoadQuickSearchSettings()
{
QuickSearchToggle.IsChecked = S.QuickSearchEnabled;
QuickSearchHotkeyBox.Text = Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey);
QuickSearchHotkeyBox.IsEnabled = S.QuickSearchEnabled;
QuickSearchStartMenuBox.IsChecked = S.QuickSearchStartMenu;
UpdateQuickSearchHint(App.QuickSearchRegistered);
}
private void QuickSearchToggle_Click(object sender, RoutedEventArgs e)
{
S.QuickSearchEnabled = QuickSearchToggle.IsChecked == true;
S.Save();
App.ApplyQuickSearchHotkey();
LoadQuickSearchSettings();
}
private void QuickSearchStartMenu_Click(object sender, RoutedEventArgs e)
{
S.QuickSearchStartMenu = QuickSearchStartMenuBox.IsChecked == true;
S.Save();
}
private void QuickSearchHotkeyBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
var key = e.Key == Key.System ? e.SystemKey : e.Key;
if (key is Key.Tab) return;
e.Handled = true;
if (key is Key.LeftCtrl or Key.RightCtrl or Key.LeftAlt or Key.RightAlt or Key.LeftShift or Key.RightShift
or Key.LWin or Key.RWin or Key.Escape) return;
int mods = (int)Keyboard.Modifiers; // Alt=1, Strg=2, Umschalt=4, Win=8 – gleiche Bits wie RegisterHotKey
if ((mods & (Hotkey.ModCtrl | Hotkey.ModAlt | Hotkey.ModWin)) == 0)
{
QuickSearchHint.Text = "Bitte mit Strg, Alt oder Win kombinieren.";
return;
}
S.QuickSearchModifiers = mods;
S.QuickSearchKey = KeyInterop.VirtualKeyFromKey(key);
S.Save();
QuickSearchHotkeyBox.Text = Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey);
UpdateQuickSearchHint(App.ApplyQuickSearchHotkey());
}
private void UpdateQuickSearchHint(bool ok) =>
QuickSearchHint.Text = !S.QuickSearchEnabled ? "Ausgeschaltet"
: ok ? $"{Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey)} öffnet von überall eine Suchleiste: ein paar Buchstaben tippen, Enter – fertig. " +
"Was du oft startest, steht weiter oben."
: "⚠ Diese Kombination ist schon von einem anderen Programm belegt – bitte ins Feld klicken und eine andere drücken.";
}
+24 -3
View File
@@ -27,7 +27,8 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<!-- Anzahl bzw. Zieh-Griff teilen sich diese Spalte – feste Mindestbreite, damit der Griff nicht ins Box-Symbol ragt -->
<ColumnDefinition Width="Auto" MinWidth="30"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Glyph}" FontFamily="{DynamicResource IconFont}" FontSize="16"
VerticalAlignment="Center" Margin="0,0,14,0">
@@ -74,7 +75,7 @@
</TextBlock>
<!-- Griff zum Umsortieren (nur beim Überfahren) -->
<TextBlock Grid.Column="3" Text="&#xE700;" FontFamily="{DynamicResource IconFont}" FontSize="14" Cursor="SizeNS"
Foreground="{DynamicResource TextSecondaryBrush}" VerticalAlignment="Center" HorizontalAlignment="Right"
Foreground="{DynamicResource TextSecondaryBrush}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="10,0,0,0"
ToolTip="Ziehen zum Umsortieren (oder Alt+↑/↓)">
<TextBlock.Style>
<Style TargetType="TextBlock">
@@ -648,6 +649,23 @@
</StackPanel>
</Border>
<TextBlock Text="Schnellstart-Suche" Style="{StaticResource SectionTitle}"/>
<Border Style="{StaticResource Card}">
<StackPanel>
<DockPanel>
<CheckBox x:Name="QuickSearchToggle" DockPanel.Dock="Right" Style="{StaticResource ToggleSwitch}" VerticalAlignment="Center" Click="QuickSearchToggle_Click"/>
<TextBox x:Name="QuickSearchHotkeyBox" DockPanel.Dock="Right" Width="200" IsReadOnly="True" Margin="0,0,16,0" IsReadOnlyCaretVisible="False"
VerticalAlignment="Center" PreviewKeyDown="QuickSearchHotkeyBox_PreviewKeyDown" ToolTip="Hier klicken und neue Tastenkombination drücken"/>
<TextBlock DockPanel.Dock="Left" Text="&#xE721;" FontFamily="{DynamicResource IconFont}" FontSize="18" VerticalAlignment="Center" Margin="0,0,16,0"/>
<StackPanel VerticalAlignment="Center" Margin="0,0,16,0">
<TextBlock Text="Programme blitzschnell starten"/>
<TextBlock x:Name="QuickSearchHint" FontSize="12" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"/>
</StackPanel>
</DockPanel>
<CheckBox x:Name="QuickSearchStartMenuBox" Content="Auch Programme aus dem Windows-Startmenü finden" Margin="34,12,0,0" Click="QuickSearchStartMenu_Click"/>
</StackPanel>
</Border>
<TextBlock Text="Desktop-Boxen" Style="{StaticResource SectionTitle}"/>
<Border Style="{StaticResource Card}">
<DockPanel>
@@ -700,7 +718,7 @@
<StackPanel VerticalAlignment="Center" Margin="0,0,16,0">
<TextBlock Text="Desktop-Seiten"/>
<TextBlock x:Name="PagesHint" FontSize="12" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"
Text="Jede Seite hat ihre eigenen Boxen. Wechseln mit Win + Alt + ← / →, mit dem Mausrad über dem freien Desktop oder über das Symbol neben der Uhr."/>
Text="Jede Seite hat ihre eigenen Boxen. Wechseln mit Win + Alt + ← / →, mit dem Mausrad über dem freien Desktop oder über das Symbol neben der Uhr. Über die Uhr bei einer Seite stellst du einen Zeitplan und ein eigenes Hintergrundbild ein."/>
</StackPanel>
</DockPanel>
<ItemsControl x:Name="PageList" Margin="34,10,0,0">
@@ -709,6 +727,9 @@
<DockPanel Margin="0,4,0,0">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Content="Anzeigen" Click="ShowPage_Click" Tag="{Binding Index}" Margin="0,0,6,0"/>
<Button Style="{StaticResource SubtleButton}" Click="PageSchedule_Click" Tag="{Binding Index}" ToolTip="Zeitplan und Hintergrundbild">
<TextBlock Text="&#xE823;" FontFamily="{DynamicResource IconFont}" FontSize="14"/>
</Button>
<Button Style="{StaticResource SubtleButton}" Click="RenamePage_Click" Tag="{Binding Index}" ToolTip="Umbenennen">
<TextBlock Text="&#xE70F;" FontFamily="{DynamicResource IconFont}" FontSize="14"/>
</Button>
+12 -2
View File
@@ -314,7 +314,9 @@ public partial class MainWindow : Window
SetEmpty(cat.Glyph, "Noch leer", $"Ziehe Icons vom Desktop hierher oder auf „{cat.Name}“ in der Seitenleiste.");
}
items = items.OrderBy(i => !Directory.Exists(i.Path))
items = (S.MainSort == "Häufig"
? items.OrderBy(i => !Directory.Exists(i.Path)).ThenByDescending(i => Usage.Score(i.Path))
: items.OrderBy(i => !Directory.Exists(i.Path)))
.ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
var px = IconPixels;
foreach (var item in items) IconLoader.Load(item, px);
@@ -456,6 +458,13 @@ public partial class MainWindow : Window
if (S.View == mode) mi.InputGestureText = "✓";
ViewMenu.Items.Add(mi);
}
ViewMenu.Items.Add(new Separator());
foreach (var (sort, label) in new[] { ("Name", "Nach Name sortieren"), ("Häufig", "Meistgenutzt zuerst") })
{
var mi = MenuItem(label, "", () => { S.MainSort = sort; S.Save(); Refresh(); }, bold: S.MainSort == sort);
if (S.MainSort == sort) mi.InputGestureText = "✓";
ViewMenu.Items.Add(mi);
}
ViewMenu.PlacementTarget = ViewButton;
ViewMenu.IsOpen = true;
}
@@ -803,7 +812,7 @@ public partial class MainWindow : Window
private void Open(string path)
{
try { Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); }
try { Usage.Start(path); }
catch (Exception ex) { Error("Öffnen fehlgeschlagen", $"„{Path.GetFileName(path)}“ konnte nicht geöffnet werden.\n\n{ex.Message}"); }
}
@@ -1208,6 +1217,7 @@ public partial class MainWindow : Window
DeadShortcutsToggle.IsChecked = S.RemoveDeadShortcuts;
LoadFenceSettings();
LoadDesktopSettings();
LoadQuickSearchSettings();
AutoRemoveToggle.IsChecked = S.AutoSortRemoveFromDesktop;
RulesList.ItemsSource = null;
RulesList.ItemsSource = S.Rules;
+97
View File
@@ -0,0 +1,97 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace DeskCore;
/// <summary>
/// Wechselt die Desktop-Seite nach Zeitplan (z. B. Mo–Fr 8–17 Uhr „Arbeit“, sonst „Freizeit“)
/// und setzt das Hintergrundbild einer Seite.
/// </summary>
public static class PageScheduler
{
/// <summary>Seite, die der Zeitplan zuletzt wollte. Gewechselt wird nur, wenn sich das ändert –
/// von Hand gewählte Seiten bleiben also bis zum nächsten Zeitplan-Wechsel stehen.</summary>
private static string? _lastWanted = "\0";
private static AppSettings S => AppSettings.Current;
/// <summary>Gibt es überhaupt einen Zeitplan?</summary>
public static bool Active => S.Pages.Count > 1 && S.Pages.Any(p => S.PageOptions.GetValueOrDefault(p)?.Scheduled == true);
/// <summary>Welche Seite laut Zeitplan jetzt dran ist (null = keine Vorgabe).</summary>
public static string? Wanted(DateTime now)
{
if (!Active) return null;
foreach (var p in S.Pages)
if (S.PageOptions.GetValueOrDefault(p)?.Matches(now) == true) return p;
return S.PageFallback is { } f && S.Pages.Contains(f, StringComparer.OrdinalIgnoreCase) ? f : null;
}
/// <summary>Regelmäßig aufrufen (alle paar Sekunden).</summary>
public static void Tick()
{
var wanted = Wanted(DateTime.Now);
if (string.Equals(wanted, _lastWanted, StringComparison.OrdinalIgnoreCase)) return;
_lastWanted = wanted;
if (wanted is null) return;
int index = S.Pages.FindIndex(p => string.Equals(p, wanted, StringComparison.OrdinalIgnoreCase));
if (index >= 0 && index != S.CurrentPage) FenceManager.SwitchPage(index);
}
/// <summary>Zeitplan geändert → beim nächsten Tick sofort anwenden.</summary>
public static void Reset() => _lastWanted = "\0";
/// <summary>Text für die Einstellungen, z. B. „Mo–Fr · 08:00–17:00“.</summary>
public static string Describe(PageOption o)
{
string[] names = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
var days = Enumerable.Range(0, 7).Where(i => (o.Days & (1 << i)) != 0).ToList();
string d = days.Count == 7 ? "Täglich"
: days.SequenceEqual([0, 1, 2, 3, 4]) ? "Mo–Fr"
: days.SequenceEqual([5, 6]) ? "Wochenende"
: days.Count > 2 && days.Last() - days.First() == days.Count - 1 ? $"{names[days.First()]}–{names[days.Last()]}"
: string.Join(", ", days.Select(i => names[i]));
return $"{d} · {o.From}–{o.To} Uhr";
}
// =====================================================================
// Hintergrundbild
// =====================================================================
/// <summary>Beim Seitenwechsel: Bild der Seite setzen – oder das ursprüngliche zurückholen.</summary>
public static void ApplyWallpaper(int pageIndex)
{
if (pageIndex < 0 || pageIndex >= S.Pages.Count) return;
var wanted = S.PageOptions.GetValueOrDefault(S.Pages[pageIndex])?.Wallpaper;
if (wanted is not null && !File.Exists(wanted)) wanted = null;
string? target;
if (wanted is not null)
{
// Vor dem ersten Wechsel das bisherige Bild merken, um es später zurückzuholen
S.SavedWallpaper ??= CurrentWallpaper() ?? "";
target = wanted;
}
else if (S.SavedWallpaper is { } saved)
{
target = saved;
S.SavedWallpaper = null;
}
else return; // Seiten ohne eigenes Bild lassen den Hintergrund in Ruhe
S.Save();
if (string.Equals(CurrentWallpaper(), target, StringComparison.OrdinalIgnoreCase)) return;
Task.Run(() => SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, target, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE));
}
public static string? CurrentWallpaper()
{
var sb = new StringBuilder(520);
return SystemParametersInfo(SPI_GETDESKWALLPAPER, (uint)sb.Capacity, sb, 0) ? sb.ToString() : null;
}
private const uint SPI_GETDESKWALLPAPER = 0x73, SPI_SETDESKWALLPAPER = 0x14, SPIF_UPDATEINIFILE = 0x1, SPIF_SENDCHANGE = 0x2;
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool SystemParametersInfo(uint action, uint param, string value, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool SystemParametersInfo(uint action, uint param, StringBuilder value, uint flags);
}
+397
View File
@@ -0,0 +1,397 @@
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace DeskCore;
/// <summary>
/// Schnellstart-Suche: Tastenkürzel → Suchleiste mitten auf dem Bildschirm, tippen, Enter, läuft.
/// Durchsucht Kategorien, Desktop, Startmenü, Start-Gruppen, Kategorien und Desktop-Seiten.
/// Was du oft startest, steht weiter oben.
/// </summary>
public sealed class QuickSearch : Window
{
private const int MaxResults = 8;
private sealed record Entry(string Name, string Sub, string? Path, string Glyph, string Key, Action Run, Action? Locate = null)
{
/// <summary>Für die Ähnlichkeit: Name ohne Endungen wie „- Verknüpfung“.</summary>
public string SearchName { get; } = Name.Replace(" - Verknüpfung", "", StringComparison.OrdinalIgnoreCase);
}
private static QuickSearch? _open;
private static List<Entry>? _startMenu;
private static DateTime _startMenuRead;
private readonly TextBox _box;
private readonly ListBox _list;
private readonly TextBlock _hint;
private List<Entry> _all = [];
private List<Entry> _shown = [];
private bool _closing;
private static AppSettings S => AppSettings.Current;
/// <summary>Per Tastenkürzel: öffnen bzw. wieder schließen.</summary>
public static void Toggle()
{
if (_open is not null) { _open.CloseNow(); return; }
_open = new QuickSearch();
_open.Show();
}
private QuickSearch()
{
var res = Application.Current.Resources;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Topmost = true;
Width = 680;
SizeToContent = SizeToContent.Height;
Title = "DeskCore Suche";
FontFamily = (FontFamily)res["UiFont"];
Foreground = (Brush)res["TextBrush"];
// Suchzeile
_box = new TextBox
{
FontSize = 20, BorderThickness = new Thickness(0), Background = Brushes.Transparent, Padding = new Thickness(0),
VerticalContentAlignment = VerticalAlignment.Center, Foreground = (Brush)res["TextBrush"],
CaretBrush = (Brush)res["TextBrush"],
};
var placeholder = new TextBlock
{
Text = "Programm, Datei, Start-Gruppe oder Kategorie suchen …", FontSize = 20, IsHitTestVisible = false,
Foreground = (Brush)res["TextTertiaryBrush"], VerticalAlignment = VerticalAlignment.Center,
};
_box.TextChanged += (_, _) =>
{
placeholder.Visibility = _box.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed;
Filter();
};
var searchRow = new DockPanel { Margin = new Thickness(18, 14, 18, 14) };
var glass = new TextBlock
{
Text = "", FontFamily = (FontFamily)res["IconFont"], FontSize = 20, Margin = new Thickness(0, 0, 14, 0),
VerticalAlignment = VerticalAlignment.Center, Foreground = (Brush)res["AccentBrush"],
};
DockPanel.SetDock(glass, Dock.Left);
searchRow.Children.Add(glass);
searchRow.Children.Add(new Grid { Children = { placeholder, _box } });
_list = new ListBox
{
BorderThickness = new Thickness(0), Background = Brushes.Transparent, Margin = new Thickness(6, 0, 6, 6),
Focusable = false, Visibility = Visibility.Collapsed,
};
ScrollViewer.SetHorizontalScrollBarVisibility(_list, ScrollBarVisibility.Disabled);
_list.MouseDoubleClick += (_, _) => RunSelected(locate: false);
_list.PreviewMouseLeftButtonUp += (_, e) =>
{
// Ein Klick genügt – wie im Startmenü
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject) is not null) RunSelected(locate: false);
};
_hint = new TextBlock
{
FontSize = 12, Margin = new Thickness(18, 8, 18, 10), Foreground = (Brush)res["TextTertiaryBrush"],
Text = "Enter Starten · Strg + Enter Speicherort öffnen · Esc Schließen",
};
var divider = new Border { Height = 1, Background = (Brush)res["ControlStrokeBrush"] };
var footer = new Border { Child = _hint, BorderBrush = (Brush)res["ControlStrokeBrush"], BorderThickness = new Thickness(0, 1, 0, 0) };
var stack = new StackPanel { Children = { searchRow, divider, _list, footer } };
Content = new Border
{
Margin = new Thickness(24), CornerRadius = new CornerRadius(10),
Background = (Brush)res["FlyoutBrush"], BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
Effect = (System.Windows.Media.Effects.Effect)res["DialogShadow"], Child = stack,
};
PreviewKeyDown += OnKey;
Deactivated += (_, _) => CloseNow();
Closed += (_, _) => { if (_open == this) _open = null; };
Loaded += (_, _) =>
{
Position();
BringToFront();
_box.Focus();
BuildIndex();
};
}
// =====================================================================
// Position & Fokus
// =====================================================================
private void Position()
{
var mouse = GetCursorPos(out var p) ? new Point(p.X / Screens.Scale, p.Y / Screens.Scale) : new Point(0, 0);
var wa = Screens.WorkAreaAt(mouse);
Left = wa.Left + (wa.Width - Width) / 2;
Top = wa.Top + wa.Height * 0.22;
}
private void BringToFront()
{
// Das Tastenkürzel erlaubt DeskCore, nach vorne zu kommen
var hwnd = new WindowInteropHelper(this).Handle;
SetForegroundWindow(hwnd);
Activate();
}
private void CloseNow()
{
if (_closing) return;
_closing = true;
Close();
}
// =====================================================================
// Was durchsucht wird
// =====================================================================
private async void BuildIndex()
{
var list = new List<Entry>();
// Start-Gruppen
foreach (var g in S.LaunchGroups)
{
var group = g;
list.Add(new Entry(g.Name, $"Start-Gruppe · {g.Items.Count} {(g.Items.Count == 1 ? "Programm" : "Programme")}", null, "",
Usage.GroupPrefix + g.Name, () => App.Win?.LaunchGroupNow(group)));
}
// Kategorien selbst (öffnet DeskCore dort) und Desktop-Seiten
foreach (var c in Storage.Categories())
{
var cat = c;
list.Add(new Entry(c, "Kategorie in DeskCore öffnen", null, S.Categories.GetValueOrDefault(c)?.Glyph ?? "",
"kategorie:" + c, () => { App.Win?.ShowFromBackground(); App.Win?.ShowCategory(cat); }));
}
if (S.Pages.Count > 1)
for (int i = 0; i < S.Pages.Count; i++)
{
int page = i;
list.Add(new Entry(S.Pages[i], "Desktop-Seite anzeigen", null, "", "seite:" + S.Pages[i], () => FenceManager.SwitchPage(page)));
}
_all = list;
Filter();
// Dateien aus Kategorien, Desktop und Startmenü im Hintergrund sammeln (Startmenü 10 Minuten zwischengespeichert)
bool withStartMenu = S.QuickSearchStartMenu;
var categories = Storage.Categories();
var files = await Task.Run(() =>
{
var l = new List<Entry>();
foreach (var c in categories)
foreach (var p in Storage.Entries(Storage.CategoryPath(c)))
l.Add(FileEntry(p, c));
foreach (var p in Storage.DesktopEntries())
l.Add(FileEntry(p, "Desktop"));
if (withStartMenu)
{
if (_startMenu is null || DateTime.Now - _startMenuRead > TimeSpan.FromMinutes(10))
{
_startMenu = ReadStartMenu();
_startMenuRead = DateTime.Now;
}
l.AddRange(_startMenu);
}
return Dedupe(l);
});
if (_closing) return;
_all = list.Concat(files).ToList();
Filter();
}
private static Entry FileEntry(string path, string where) =>
new(Storage.DisplayName(path), where, path, "", path, () => Usage.Start(path),
() => Process.Start("explorer.exe", $"/select,\"{path}\""));
private static List<Entry> ReadStartMenu()
{
var result = new List<Entry>();
foreach (var root in new[]
{
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
})
{
if (!Directory.Exists(root)) continue;
IEnumerable<string> files;
try { files = Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).ToList(); }
catch { continue; }
foreach (var f in files)
{
var ext = Path.GetExtension(f).ToLowerInvariant();
if (ext is not (".lnk" or ".url" or ".appref-ms")) continue;
var name = Path.GetFileNameWithoutExtension(f);
// Deinstallations- und Hilfe-Verknüpfungen will niemand per Suche starten
if (name.Contains("uninstall", StringComparison.OrdinalIgnoreCase) || name.Contains("deinstall", StringComparison.OrdinalIgnoreCase))
continue;
result.Add(new Entry(name, "Startmenü", f, "", f, () => Usage.Start(f),
() => Process.Start("explorer.exe", $"/select,\"{f}\"")));
}
}
return result;
}
/// <summary>
/// Gleiches Programm aus Kategorie, Desktop und Startmenü nur einmal zeigen – die Reihenfolge der Quellen
/// entscheidet (Kategorie vor Desktop vor Startmenü). Gleicher Name gilt als gleiches Programm.
/// </summary>
private static List<Entry> Dedupe(IEnumerable<Entry> entries)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
return entries.Where(e => e.Path is null || seen.Add(e.SearchName.Trim())).ToList();
}
// =====================================================================
// Suchen & Anzeigen
// =====================================================================
/// <summary>Wie gut passt der Name zur Eingabe? -1 = gar nicht.</summary>
private static int Match(string name, string q)
{
if (q.Length == 0) return 0;
var n = name.ToLowerInvariant();
if (n == q) return 1000;
if (n.StartsWith(q)) return 800;
var words = n.Split([' ', '-', '_', '.', '(', ')', '&'], StringSplitOptions.RemoveEmptyEntries);
if (words.Any(w => w.StartsWith(q))) return 650;
if (q.Length >= 2 && string.Concat(words.Select(w => w[0])).StartsWith(q)) return 600; // "vsc" → Visual Studio Code
if (n.Contains(q)) return 450;
// Buchstaben in der richtigen Reihenfolge ("fzla" → FileZilla) – aber nur ab einem Wortanfang,
// sonst passt fast alles irgendwie ("ets" → Malwarebytes)
if (q.Length < 3) return -1;
for (int start = 0; start < n.Length; start++)
{
if (n[start] != q[0] || (start > 0 && char.IsLetterOrDigit(n[start - 1]))) continue;
int k = 0;
for (int j = start; j < n.Length && k < q.Length; j++) if (n[j] == q[k]) k++;
if (k == q.Length) return 200;
}
return -1;
}
private void Filter()
{
var q = _box.Text.Trim().ToLowerInvariant();
if (q.Length == 0)
{
// Ohne Eingabe: zuletzt/oft benutzt
_shown = _all.Where(e => Usage.Score(e.Key) > 0)
.OrderByDescending(e => Usage.Score(e.Key)).Take(MaxResults).ToList();
}
else
{
_shown = _all.Select(e => (e, m: Match(e.SearchName, q)))
.Where(x => x.m >= 0)
.OrderByDescending(x => x.m + Math.Min(300, Usage.Score(x.e.Key) * 40))
.ThenBy(x => x.e.Name.Length)
.Take(MaxResults).Select(x => x.e).ToList();
}
ShowResults(q.Length > 0);
}
private void ShowResults(bool searching)
{
var res = Application.Current.Resources;
_list.Items.Clear();
int px = (int)Math.Round(32 * VisualTreeHelper.GetDpi(this).DpiScaleX);
foreach (var e in _shown)
{
FrameworkElement icon;
if (e.Path is not null)
{
var img = new Image { Width = 32, Height = 32 };
var item = new IconItem { Path = e.Path };
item.PropertyChanged += (_, _) => img.Source = item.Icon;
IconLoader.Load(item, px);
img.Source = item.Icon;
icon = img;
}
else
{
var meta = e.Key.StartsWith("kategorie:") ? S.Categories.GetValueOrDefault(e.Name) : null;
icon = new TextBlock
{
Text = e.Glyph, FontFamily = (FontFamily)res["IconFont"], FontSize = 22, Width = 32, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Foreground = CategoryColors.Brush(meta?.Color) ?? (Brush)res["AccentBrush"],
};
}
icon.Margin = new Thickness(0, 0, 14, 0);
DockPanel.SetDock(icon, Dock.Left);
var texts = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
texts.Children.Add(new TextBlock { Text = e.Name, FontSize = 15, TextTrimming = TextTrimming.CharacterEllipsis });
texts.Children.Add(new TextBlock { Text = e.Sub, FontSize = 12, Foreground = (Brush)res["TextSecondaryBrush"] });
_list.Items.Add(new DockPanel { Margin = new Thickness(8, 6, 8, 6), Children = { icon, texts } });
}
if (_shown.Count == 0 && searching)
_list.Items.Add(new TextBlock
{
Text = "Nichts gefunden", Margin = new Thickness(12, 10, 12, 10), Foreground = (Brush)res["TextSecondaryBrush"],
IsHitTestVisible = false,
});
_list.Visibility = _list.Items.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
if (_shown.Count > 0) _list.SelectedIndex = 0;
}
private void OnKey(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape:
CloseNow();
e.Handled = true;
break;
case Key.Down or Key.Up when _shown.Count > 0:
int i = _list.SelectedIndex + (e.Key == Key.Down ? 1 : -1);
_list.SelectedIndex = (i + _shown.Count) % _shown.Count;
_list.ScrollIntoView(_list.SelectedItem);
e.Handled = true;
break;
case Key.Enter:
RunSelected(locate: Keyboard.Modifiers.HasFlag(ModifierKeys.Control));
e.Handled = true;
break;
}
}
private void RunSelected(bool locate)
{
if (_list.SelectedIndex < 0 || _list.SelectedIndex >= _shown.Count) return;
var entry = _shown[_list.SelectedIndex];
CloseNow(); // zuerst schließen, damit das gestartete Programm nach vorne kommt
try
{
if (locate && entry.Locate is not null) entry.Locate();
else
{
entry.Run();
// Kategorien/Seiten mitzählen (Dateien zählt Usage.Start, Gruppen LaunchGroupNow selbst)
if (entry.Path is null && !entry.Key.StartsWith(Usage.GroupPrefix)) Usage.Record(entry.Key);
}
}
catch (Exception ex)
{
App.TrayIcon?.Notify("Starten fehlgeschlagen", $"„{entry.Name}“ konnte nicht geöffnet werden.\n{ex.Message}");
}
}
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p);
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
}
+78 -10
View File
@@ -31,6 +31,7 @@ Danach kannst du die Icons vom Desktop löschen. In DeskCore bleiben sie erhalte
- [Installieren](#installieren)
- [So räumst du deinen Desktop auf](#so-räumst-du-deinen-desktop-auf)
- [Programme starten](#programme-starten)
- [Schnellstart-Suche](#schnellstart-suche)
- [Start-Gruppen – mehrere Programme mit einem Klick](#start-gruppen--mehrere-programme-mit-einem-klick)
- [Boxen auf dem Desktop](#boxen-auf-dem-desktop)
- [Desktop-Seiten](#desktop-seiten)
@@ -51,9 +52,11 @@ Danach kannst du die Icons vom Desktop löschen. In DeskCore bleiben sie erhalte
|---|---|
| 🗂️ **Eigene Kategorien** | mit Symbol und Farbe, Reihenfolge frei wählbar |
| ✅ **Sicher aufräumen** | Icons werden gesichert, bevor du sie vom Desktop löschst |
| 🚀 **Schnellstart-Suche** | Alt + Umschalt + Leertaste, ein paar Buchstaben, Enter – läuft |
| ▶️ **Start-Gruppen** | ein Klick startet mehrere Programme auf einmal |
| 🧱 **Desktop-Boxen** | Kategorien oder Ordner als Kästen direkt auf dem Desktop, mit Tabs und eigenem Aussehen |
| 📑 **Desktop-Seiten** | mehrere Desktops zum Umschalten, z. B. „Arbeit“ und „Freizeit“ |
| 📑 **Desktop-Seiten** | mehrere Desktops zum Umschalten, z. B. „Arbeit“ und „Freizeit“ – auch automatisch nach Uhrzeit |
| ⭐ **Meistgenutzt zuerst** | DeskCore merkt sich, was du oft startest, und sortiert danach |
| 🎨 **Icons einfärben** | alle Desktop-Icons in einem einheitlichen Farbton |
| 👁️ **Schnell verstecken** | Doppelklick auf den Desktop – alles weg, noch ein Doppelklick – alles wieder da |
| ⚡ **Automatisch sortieren** | neue Icons landen per Regel von selbst in der richtigen Kategorie |
@@ -76,21 +79,22 @@ Danach kannst du die Icons vom Desktop löschen. In DeskCore bleiben sie erhalte
## Installieren
1. Doppelklick auf **`DeskCore-Setup.exe`**
2. **Weiter** klicken
3. Die Lizenz lesen, den Haken bei **„Ich akzeptiere …“** setzen, dann **Weiter**
4. Bei Bedarf noch auswählen:
2. Windows fragt: **„Möchten Sie zulassen, dass durch diese App Änderungen an Ihrem Gerät vorgenommen werden?“** → **Ja**
3. **Weiter** klicken
4. Die Lizenz lesen, den Haken bei **„Ich akzeptiere …“** setzen, dann **Weiter**
5. Bei Bedarf noch auswählen:
- Verknüpfung auf dem Desktop (empfohlen)
- Mit Windows starten
5. **Installieren** klicken, und fertig.
6. **Installieren** klicken, und fertig.
Du brauchst dafür **keine Administratorrechte**.
Nur wenn „.NET“ fehlt und installiert wird, fragt Windows einmal nach. Einfach mit **Ja** bestätigen.
Der Installer braucht **Administratorrechte**, weil er bei Bedarf die von DeskCore benötigte Microsoft-Laufzeit („.NET“) gleich mitinstalliert.
DeskCore selbst läuft danach ganz normal **ohne** Administratorrechte.
> **Windows warnt beim Start des Installers?**
> Bei neuen Programmen zeigt Windows manchmal „Der Computer wurde durch Windows geschützt“.
> Klick dann auf **„Weitere Informationen“** und danach auf **„Trotzdem ausführen“**.
**Update auf eine neue Version:** Einfach den neuen Installer starten. Deine Kategorien und Einstellungen bleiben erhalten.
**Update auf eine neue Version:** Einfach den neuen Installer starten – oder in DeskCore auf **„Jetzt aktualisieren“** klicken und die Windows-Abfrage mit **Ja** bestätigen. Deine Kategorien und Einstellungen bleiben erhalten.
---
@@ -136,6 +140,36 @@ Die Suche findet Programme in allen Kategorien und auf dem Desktop.
Auch über das **DeskCore-Symbol neben der Uhr** kommst du per Rechtsklick direkt an deine Kategorien.
**Meistgenutzt zuerst:** DeskCore merkt sich, was du wie oft startest. Über den **Ansicht-Knopf** oben rechts →
**„Meistgenutzt zuerst“** stehen deine Lieblingsprogramme in jeder Kategorie ganz vorne.
Was du lange nicht mehr benutzt hast, rutscht mit der Zeit von selbst wieder nach hinten.
---
## Schnellstart-Suche
Programme starten, ohne DeskCore zu öffnen – ähnlich wie die Spotlight-Suche am Mac:
1. **Alt + Umschalt + Leertaste** drücken – egal, was gerade offen ist
2. Ein paar Buchstaben tippen
3. **Enter** – das Programm startet
Gefunden wird alles aus deinen **Kategorien**, vom **Desktop** und aus dem **Windows-Startmenü**,
dazu deine **Start-Gruppen**, **Kategorien** (öffnet DeskCore dort) und **Desktop-Seiten**.
Was du oft startest, steht weiter oben. Ohne Eingabe siehst du direkt deine meistgenutzten Programme.
Die Suche ist großzügig: **„vsc“** findet **V**isual **S**tudio **C**ode, **„fzla“** findet **F**ile**Z**i**l**l**a**.
| Taste | Was passiert |
|---|---|
| **↑ / ↓** | Treffer auswählen |
| **Enter** | Starten |
| **Strg + Enter** | Speicherort im Explorer öffnen |
| **Esc** | Schließen (oder einfach woanders hinklicken) |
Das Tastenkürzel änderst du unter **Einstellungen → Schnellstart-Suche** (ins Feld klicken, neue Kombination drücken).
Dort kannst du auch einstellen, ob das Windows-Startmenü mit durchsucht wird.
---
## Start-Gruppen – mehrere Programme mit einem Klick
@@ -184,20 +218,28 @@ Es gibt drei Wege:
- **Programm starten:** Doppelklick auf das Icon
- **Rechtsklick auf ein Icon:** Öffnen, Umbenennen, Löschen, in eine andere Kategorie verschieben oder **„Zurück auf den Desktop legen“**
- Icons lassen sich auch einfach in eine Box hinein- oder aus ihr herausziehen
- **Eigene Reihenfolge:** Zieh ein Icon innerhalb der Box an die gewünschte Stelle – ein blauer Strich zeigt, wo es landet.
Die Box merkt sich die Reihenfolge (Sortierung „Eigene“).
Das **Menü einer Box** (die drei Punkte ⋯ oben rechts) bietet:
| Menüpunkt | Was passiert |
|---|---|
| **Tab hinzufügen** | Eine weitere Kategorie in dieselbe Box packen. Oben erscheinen dann Reiter zum Umschalten. |
| **Tab hinzufügen** | Eine weitere Kategorie, ein Ordner-Portal oder **„Zuletzt benutzt“** in dieselbe Box packen. Oben erscheinen dann Reiter zum Umschalten. |
| **Ordner-Portal …** | Einen Ordner als Reiter hinzufügen (siehe unten) |
| **Aussehen …** | Farbe, Deckkraft, Milchglas, Icon-Größe, Sortierung und Icon-Tönung der Box |
| **Aussehen …** | Farbe, Deckkraft, Milchglas, Icon-Größe, Icon-Tönung und Sortierung der Box: nach Name, Typ, Datum, **Meistgenutzt** oder **Eigene** Reihenfolge |
| **Sperren** | Die Box lässt sich nicht mehr versehentlich verschieben oder in der Größe ändern |
| **Auf Seite verschieben** | Die Box auf eine andere [Desktop-Seite](#desktop-seiten) schicken |
| **Auf Seite kopieren** | Die gleiche Box zusätzlich auf eine andere Seite legen – z. B. „Internet“ auf „Arbeit“ **und** „Freizeit“ |
| **Box schließen** | Die Box verschwindet vom Desktop. Die Kategorie in DeskCore bleibt natürlich erhalten. |
Rechtsklick auf einen **Reiter**: **„Als eigene Box lösen“** macht daraus wieder eine eigene Box, **„Tab entfernen“** nimmt ihn heraus.
### Box „Zuletzt benutzt“
Eine Box, die sich von selbst füllt: Sie zeigt, was du zuletzt über DeskCore gestartet hast – neueste zuerst.
Anlegen: beim **Aufziehen einer Box** „Zuletzt benutzt“ wählen, oder im Box-Menü ⋯ → **Tab hinzufügen → Zuletzt benutzt**.
Mitgezählt wird alles, was du über DeskCore startest: aus dem Fenster, aus Boxen, über die Schnellstart-Suche und über Start-Gruppen.
### Ordner-Portal
Ein Portal ist eine Box, die **live einen beliebigen Ordner** zeigt, z. B. **Downloads** oder einen Projektordner.
Neue Dateien erscheinen sofort darin, und Unterordner öffnest du direkt in der Box.
@@ -234,8 +276,28 @@ Dort kannst du Seiten auch umbenennen (Stift) oder löschen (Mülleimer). Die Bo
Beim Wechseln zeigt DeskCore kurz unten am Bildschirm, auf welcher Seite du bist.
Neue Boxen landen immer auf der Seite, die gerade angezeigt wird. Eine Box auf eine andere Seite schicken: Box-Menü ⋯ → **„Auf Seite verschieben“**.
**Eine Kategorie auf mehreren Seiten:** Jede Seite darf ihre eigene Box mit derselben Kategorie haben – z. B. „Internet“ auf „Arbeit“ und auf „Freizeit“.
Am schnellsten geht das über das Box-Menü ⋯ → **„Auf Seite kopieren“**. Es sind dieselben Icons: Was du in der einen Box änderst, siehst du auch in der anderen.
Nur **auf derselben Seite** steht eine Kategorie in höchstens einer Box.
> Die Icons, die direkt auf dem Desktop liegen (nicht in einer Box), siehst du auf jeder Seite.
### Seiten automatisch wechseln (Zeitplan)
Klick in den Einstellungen bei einer Seite auf die **Uhr** 🕒:
- **„Diese Seite automatisch anzeigen“** – Tage und Uhrzeit wählen, z. B. **Mo–Fr, 08:00 bis 17:00**
- **„Zu allen anderen Zeiten diese Seite anzeigen“** – für die Seite, die sonst gelten soll
Beispiel: „Arbeit“ bekommt Mo–Fr 08:00–17:00, bei „Freizeit“ setzt du den Haken für „alle anderen Zeiten“.
Morgens um acht wechselt DeskCore dann von selbst auf „Arbeit“, um fünf zurück auf „Freizeit“.
Wechselst du zwischendurch von Hand, bleibt deine Wahl bis zum nächsten geplanten Wechsel stehen.
Zeiträume über Mitternacht (z. B. 22:00 bis 06:00) gehen auch.
### Eigenes Hintergrundbild je Seite
Im selben Fenster (**Uhr** 🕒) unter **„Hintergrundbild“** → **„Bild wählen …“**.
Beim Wechsel auf die Seite stellt DeskCore das Bild ein. Wechselst du auf eine Seite ohne eigenes Bild,
kommt dein ursprünglicher Hintergrund zurück.
---
## Den Desktop schick machen
@@ -316,6 +378,7 @@ Prüfen von Hand: **Einstellungen → Updates → „Jetzt prüfen“**.
| Taste | Was passiert |
|---|---|
| **Alt + Umschalt + Leertaste** | Schnellstart-Suche |
| **Win + Umschalt + D** | DeskCore öffnen / verstecken |
| **Win + Umschalt + B** | Peek: Boxen nach vorne holen |
| **Win + Alt + ← / →** | Desktop-Seite wechseln |
@@ -385,6 +448,10 @@ Steckst du den Bildschirm wieder an, kommen die Boxen an ihre alten Plätze.
**Einstellungen → Sicherung → „Sichern …“** speichert alles in eine ZIP-Datei.
Auf dem neuen PC installierst du DeskCore und wählst **„Wiederherstellen …“**.
**Merkt sich DeskCore, welche Programme ich starte? Geht das irgendwohin?**
Nur auf deinem PC: in der Datei `usage.json` im DeskCore-Einstellungsordner. Gezählt wird nur, was du **über DeskCore** startest.
Nichts davon verlässt deinen Rechner.
**Ein Tastenkürzel funktioniert nicht.**
Ein anderes Programm benutzt es vielleicht schon. Unter **Einstellungen → Tastenkürzel** ins Feld klicken und eine andere Kombination drücken.
@@ -398,6 +465,7 @@ Ja, für Fortgeschrittene:
| `DeskCore.exe --page "Freizeit"` | Zur Desktop-Seite „Freizeit“ wechseln (oder `--page 2`) |
| `DeskCore.exe --toggle-desktop` | Desktop aus- / einblenden |
| `DeskCore.exe --peek` | Boxen nach vorne holen |
| `DeskCore.exe --search` | Schnellstart-Suche öffnen |
---
+9
View File
@@ -26,6 +26,15 @@ public static class Screens
public static Rect PrimaryWorkArea => SystemParameters.WorkArea;
/// <summary>Arbeitsbereich des Bildschirms, auf dem der Punkt liegt (sonst der nächstgelegene).</summary>
public static Rect WorkAreaAt(Point p)
{
var areas = WorkAreas();
return areas.FirstOrDefault(a => a.Contains(p), areas.OrderBy(a =>
Math.Pow(Math.Clamp(p.X, a.Left, a.Right) - p.X, 2) + Math.Pow(Math.Clamp(p.Y, a.Top, a.Bottom) - p.Y, 2))
.FirstOrDefault(PrimaryWorkArea));
}
/// <summary>Ist genug vom Fenster (Titelleiste) auf irgendeinem Bildschirm sichtbar, um es greifen zu können?</summary>
public static bool IsReachable(Rect window)
{
+91 -10
View File
@@ -40,15 +40,45 @@ public class SortRule
[JsonIgnore] public string KindText => Kind == "Typ" ? "Dateityp ist" : "Name enthält";
}
/// <summary>Ein Tab einer Desktop-Box: eine Kategorie oder ein beliebiger Ordner ("Ordner-Portal").</summary>
/// <summary>Ein Tab einer Desktop-Box: eine Kategorie, ein beliebiger Ordner ("Ordner-Portal") oder "Zuletzt benutzt".</summary>
public class FenceTab
{
public const string CategoryKind = "Kategorie", FolderKind = "Ordner";
public const string CategoryKind = "Kategorie", FolderKind = "Ordner", RecentKind = "Zuletzt";
public string Kind { get; set; } = CategoryKind;
/// <summary>Kategoriename bzw. Ordnerpfad.</summary>
/// <summary>Kategoriename bzw. Ordnerpfad (bei "Zuletzt benutzt" leer).</summary>
public string Value { get; set; } = "";
/// <summary>Eigene Reihenfolge (Dateinamen), wenn die Box nach "Eigene" sortiert ist.</summary>
public List<string> Order { get; set; } = [];
[JsonIgnore] public bool IsFolder => Kind == FolderKind;
[JsonIgnore] public string Title => IsFolder ? Path.GetFileName(Value.TrimEnd('\\')) is { Length: > 0 } n ? n : Value : Value;
[JsonIgnore] public bool IsRecent => Kind == RecentKind;
[JsonIgnore] public bool IsCategory => Kind == CategoryKind;
[JsonIgnore] public string Title => IsRecent ? "Zuletzt benutzt"
: IsFolder ? Path.GetFileName(Value.TrimEnd('\\')) is { Length: > 0 } n ? n : Value : Value;
}
/// <summary>Zusatzeinstellungen einer Desktop-Seite: Zeitplan und Hintergrundbild.</summary>
public class PageOption
{
/// <summary>Seite automatisch zu bestimmten Zeiten anzeigen.</summary>
public bool Scheduled { get; set; }
/// <summary>Wochentage als Bits: Montag = 1, Dienstag = 2 … Sonntag = 64.</summary>
public int Days { get; set; } = 0b0011111; // Mo–Fr
public string From { get; set; } = "08:00";
public string To { get; set; } = "17:00";
/// <summary>Eigenes Hintergrundbild für diese Seite (null = nicht ändern).</summary>
public string? Wallpaper { get; set; }
public static int DayBit(DayOfWeek d) => 1 << (((int)d + 6) % 7); // Montag = Bit 0
/// <summary>Gilt der Zeitplan zu diesem Zeitpunkt? Zeiträume über Mitternacht (z. B. 22:00–06:00) gehen auch.</summary>
public bool Matches(DateTime now)
{
if (!Scheduled || !TimeSpan.TryParse(From, out var from) || !TimeSpan.TryParse(To, out var to)) return false;
var t = now.TimeOfDay;
if (from <= to) return (Days & DayBit(now.DayOfWeek)) != 0 && t >= from && t < to;
// über Mitternacht: der Tag zählt, an dem der Zeitraum beginnt
return t >= from ? (Days & DayBit(now.DayOfWeek)) != 0 : t < to && (Days & DayBit(now.AddDays(-1).DayOfWeek)) != 0;
}
}
/// <summary>Eine Desktop-Box mit Position, Tabs und Aussehen.</summary>
@@ -71,7 +101,7 @@ public class FenceDef
/// <summary>Milchglas-Effekt (Hintergrund verschwommen).</summary>
public bool Blur { get; set; }
public ViewMode IconSize { get; set; } = ViewMode.Medium;
/// <summary>"Name", "Typ" oder "Datum"</summary>
/// <summary>"Name", "Typ", "Datum", "Häufig" (meistgenutzt zuerst) oder "Eigene" (per Ziehen angeordnet)</summary>
public string Sort { get; set; } = "Name";
/// <summary>Icon-Tönung (#RRGGBB) oder null.</summary>
public string? Tint { get; set; }
@@ -128,6 +158,22 @@ public class AppSettings
public int CurrentPage { get; set; }
/// <summary>Mausrad über einer freien Desktop-Stelle wechselt die Seite.</summary>
public bool PageWheel { get; set; } = true;
/// <summary>Zeitplan und Hintergrundbild je Seite (Schlüssel = Seitenname).</summary>
public Dictionary<string, PageOption> PageOptions { get; set; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Seite, die außerhalb aller Zeitpläne angezeigt wird (null = keine).</summary>
public string? PageFallback { get; set; }
/// <summary>Hintergrundbild, das vor dem ersten Seiten-Hintergrund eingestellt war (zum Zurückholen).</summary>
public string? SavedWallpaper { get; set; }
/// <summary>Schnellstart-Suche per Tastenkürzel.</summary>
public bool QuickSearchEnabled { get; set; } = true;
public int QuickSearchModifiers { get; set; } = Hotkey.ModAlt | Hotkey.ModShift;
public int QuickSearchKey { get; set; } = 0x20; // Leertaste
/// <summary>Programme aus dem Windows-Startmenü mit durchsuchen.</summary>
public bool QuickSearchStartMenu { get; set; } = true;
/// <summary>Sortierung im Hauptfenster: "Name" oder "Häufig".</summary>
public string MainSort { get; set; } = "Name";
/// <summary>Rechte Maustaste auf freier Desktop-Fläche ziehen → neue Box.</summary>
public bool DrawFences { get; set; } = true;
@@ -191,6 +237,8 @@ public class AppSettings
s.Fences ??= [];
s.Snapshots ??= [];
s.ScreenLayouts ??= [];
s.PageOptions = new(s.PageOptions ?? [], StringComparer.OrdinalIgnoreCase);
foreach (var t in s.Fences.SelectMany(f => f.Tabs)) t.Order ??= [];
if (s.Pages is null || s.Pages.Count == 0) s.Pages = ["Seite 1"];
s.CurrentPage = Math.Clamp(s.CurrentPage, 0, s.Pages.Count - 1);
@@ -257,11 +305,12 @@ public class AppSettings
g.Items[i] = newDir + g.Items[i][oldDir.Length..];
}
/// <summary>Wird diese Kategorie in irgendeiner Box angezeigt?</summary>
/// <summary>Liegt diese Kategorie auf der gerade angezeigten Desktop-Seite in einer Box?</summary>
public bool HasFence(string category) =>
Fences.Any(f => f.Tabs.Any(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase)));
Fences.Any(f => f.Page == CurrentPage &&
f.Tabs.Any(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase)));
/// <summary>Kategorie als eigene Box anzeigen bzw. aus allen Boxen entfernen.</summary>
/// <summary>Kategorie auf der aktuellen Seite als eigene Box anzeigen bzw. von dieser Seite entfernen (andere Seiten bleiben).</summary>
public void SetFence(string category, bool on)
{
if (on)
@@ -271,8 +320,8 @@ public class AppSettings
}
else
{
foreach (var f in Fences)
f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
foreach (var f in Fences.Where(f => f.Page == CurrentPage))
f.Tabs.RemoveAll(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
Fences.RemoveAll(f => f.Tabs.Count == 0);
}
}
@@ -288,9 +337,41 @@ public class AppSettings
foreach (var g in LaunchGroups)
for (int i = 0; i < g.Items.Count; i++)
if (string.Equals(g.Items[i], oldPath, StringComparison.OrdinalIgnoreCase)) { g.Items[i] = newPath; changed = true; }
// Umbenannt im selben Ordner → Platz in der eigenen Box-Reihenfolge behalten
if (Storage.SamePath(Path.GetDirectoryName(oldPath), Path.GetDirectoryName(newPath)))
{
string oldName = Path.GetFileName(oldPath), newName = Path.GetFileName(newPath);
foreach (var t in Fences.SelectMany(f => f.Tabs))
for (int i = 0; i < t.Order.Count; i++)
if (string.Equals(t.Order[i], oldName, StringComparison.OrdinalIgnoreCase)) { t.Order[i] = newName; changed = true; }
}
Usage.Rename(oldPath, newPath);
if (changed) Save();
}
// ---------- Desktop-Seiten ----------
public PageOption PageOption(string page)
{
if (!PageOptions.TryGetValue(page, out var o)) PageOptions[page] = o = new PageOption();
return o;
}
public void RenamePage(int index, string newName)
{
var old = Pages[index];
Pages[index] = newName;
if (PageOptions.Remove(old, out var o)) PageOptions[newName] = o;
if (string.Equals(PageFallback, old, StringComparison.OrdinalIgnoreCase)) PageFallback = newName;
}
/// <summary>Aufräumen nach dem Löschen einer Seite (Boxen verschiebt der FenceManager).</summary>
public void ForgetPage(string name)
{
PageOptions.Remove(name);
if (string.Equals(PageFallback, name, StringComparison.OrdinalIgnoreCase)) PageFallback = null;
}
public void RemoveCategory(string name)
{
foreach (var f in Fences)
+1 -1
View File
@@ -27,7 +27,7 @@ public partial class App : Application
Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists,
autostart: Installer.AutostartEnabled, progress, installRuntime: true);
// nach einem Update aus DeskCore heraus: gleich wieder starten
if (launch) System.Diagnostics.Process.Start(System.IO.Path.Combine(dir, Installer.ExeName));
if (launch) Installer.StartUnelevated(System.IO.Path.Combine(dir, Installer.ExeName));
}
Shutdown(0);
}
+2
View File
@@ -15,6 +15,8 @@
<AssemblyName>DeskCore-Setup</AssemblyName>
<RootNamespace>DeskCoreSetup</RootNamespace>
<ApplicationIcon>..\app.ico</ApplicationIcon>
<!-- Installer fragt beim Start einmal nach Administratorrechten (für die .NET-Laufzeit von Microsoft) -->
<ApplicationManifest>app.manifest</ApplicationManifest>
<Product>DeskCore Setup</Product>
<AssemblyTitle>DeskCore Setup</AssemblyTitle>
<Description>Installiert DeskCore</Description>
+14 -1
View File
@@ -4,9 +4,22 @@ using Microsoft.Win32;
namespace DeskCoreSetup;
/// <summary>Installation/Deinstallation – nur für den aktuellen Benutzer, ohne Administratorrechte.</summary>
/// <summary>
/// Installation/Deinstallation für den aktuellen Benutzer. Der Installer selbst läuft mit Administratorrechten
/// (wegen der .NET-Laufzeit von Microsoft), DeskCore wird aber immer ohne gestartet.
/// </summary>
public static class Installer
{
/// <summary>
/// DeskCore ohne Administratorrechte starten – über den Explorer, der als normaler Benutzer läuft.
/// Wichtig: Ein Programm mit Adminrechten darf von Windows aus keine Icons per Ziehen vom Desktop annehmen.
/// </summary>
public static void StartUnelevated(string exe)
{
try { Process.Start(new ProcessStartInfo("explorer.exe", $"\"{exe}\"") { UseShellExecute = false }); }
catch { }
}
public const string AppName = "DeskCore";
public const string Publisher = "M_Viper";
public const string ExeName = "DeskCore.exe";
+1 -1
View File
@@ -78,7 +78,7 @@
<TextBlock Style="{StaticResource Body}"
Text="DeskCore sortiert deine Desktop-Icons in eigene Kategorien – mit Suche, Desktop-Boxen, automatischem Einsortieren und Rückgängig-Funktion."/>
<TextBlock Style="{StaticResource Body}" Margin="0,14,0,0"
Text="Der Assistent installiert DeskCore nur für dein Benutzerkonto. Administratorrechte sind nicht nötig."/>
Text="Der Assistent installiert DeskCore für dein Benutzerkonto und – falls nötig – die benötigte Laufzeit von Microsoft. DeskCore selbst läuft danach ganz normal ohne Administratorrechte."/>
<Border x:Name="RuntimeHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
<DockPanel>
+2 -2
View File
@@ -32,7 +32,7 @@ public partial class MainWindow : Window
RuntimeBox.Visibility = Visibility.Visible;
RuntimeHintText.Text = Runtime.Is64BitWindows
? "DeskCore benötigt die kostenlose „Microsoft .NET 9 Desktop Runtime“. Sie fehlt auf diesem PC und wird bei der " +
"Installation automatisch von Microsoft geladen (ca. 55 MB). Windows fragt dabei einmal nach Administratorrechten."
"Installation automatisch von Microsoft geladen (ca. 55 MB) und mitinstalliert."
: "DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11) und kann auf diesem PC leider nicht installiert werden.";
}
@@ -121,7 +121,7 @@ public partial class MainWindow : Window
break;
case 3:
if (LaunchBox.IsChecked == true && _installedExe is not null)
Process.Start(new ProcessStartInfo(_installedExe) { UseShellExecute = true });
Installer.StartUnelevated(_installedExe); // ohne Adminrechte, sonst klappt Ziehen vom Desktop nicht
Close();
break;
}
+20 -3
View File
@@ -20,6 +20,19 @@ public static class Runtime
public static bool Is64BitWindows => Environment.Is64BitOperatingSystem;
public static bool IsElevated
{
get
{
try
{
using var id = System.Security.Principal.WindowsIdentity.GetCurrent();
return new System.Security.Principal.WindowsPrincipal(id).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
catch { return false; }
}
}
public static bool IsInstalled
{
get
@@ -43,7 +56,7 @@ public static class Runtime
}
}
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (Windows fragt einmal nach Administratorrechten).</summary>
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (der Installer läuft bereits mit Administratorrechten).</summary>
public static void Install(Action<double, string> progress)
{
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
@@ -77,11 +90,15 @@ public static class Runtime
throw new InvalidOperationException("Die heruntergeladene Datei ist nicht gültig von Microsoft signiert und wurde nicht ausgeführt.");
}
progress(1, $"{DisplayName} wird installiert – bitte die Windows-Abfrage bestätigen …");
progress(1, $"{DisplayName} wird installiert …");
Process p;
try
{
p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart") { UseShellExecute = true, Verb = "runas" })!;
// Der Installer hat schon Adminrechte – "runas" nur als Absicherung, falls er doch ohne gestartet wurde
p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart")
{
UseShellExecute = true, Verb = IsElevated ? "" : "runas", WindowStyle = ProcessWindowStyle.Hidden,
})!;
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
+24
View File
@@ -0,0 +1,24 @@
<?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="DeskCore.Setup"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- Der Installer läuft mit Administratorrechten: so kann er die von Microsoft benötigte
.NET-Laufzeit ohne zweite Nachfrage mitinstallieren. DeskCore selbst startet danach ohne Adminrechte. -->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 und 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
@@ -5,7 +5,7 @@
},
"projects": {
"C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj": {
"version": "1.0.2",
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"projectName": "DeskCore-Setup",
Binary file not shown.
Binary file not shown.
@@ -14,11 +14,11 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert DeskCore")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.2.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.2")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.4.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.4")]
[assembly: System.Reflection.AssemblyProductAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyTitleAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.2.0")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.4.0")]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
// Von der MSBuild WriteCodeFragment-Klasse generiert.
@@ -1 +1 @@
998ec94b85fc0b36797c21782b2eeb7956eea9290fb02db5dd064534e4226df4
d2e033b6ce1399c33b5b36da4d426df28d32a528502994c6fd13241554523b24
@@ -1,5 +1,5 @@
is_global = true
build_property.ApplicationManifest =
build_property.ApplicationManifest = app.manifest
build_property.StartupObject =
build_property.ApplicationDefaultFont =
build_property.ApplicationHighDpiMode =
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "35944FD50A7EBB591A07AA8A05B692F31E01686F"
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "1A3CEC98E280296CC3F91E26F8B8AC24476C46EF"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
+1 -1
View File
@@ -418,7 +418,7 @@
"C:\\Users\\Viper\\.nuget\\packages\\": {}
},
"project": {
"version": "1.0.2",
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"projectName": "DeskCore-Setup",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"version": 2,
"dgSpecHash": "B0FFLxz3ggo=",
"dgSpecHash": "1d5+c4eGnFw=",
"success": true,
"projectFilePath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"expectedPackageFiles": [
+120
View File
@@ -0,0 +1,120 @@
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Windows.Threading;
namespace DeskCore;
public class UsageEntry
{
public int Count { get; set; }
public DateTime Last { get; set; }
}
/// <summary>
/// Merkt sich, was wie oft und wann zuletzt über DeskCore gestartet wurde (für „Meistgenutzt“, „Zuletzt benutzt“
/// und die Schnellstart-Suche). Liegt getrennt von den Einstellungen in usage.json, weil es sich ständig ändert.
/// </summary>
public static class Usage
{
/// <summary>Schlüssel-Präfix für Start-Gruppen (die keinen Dateipfad haben).</summary>
public const string GroupPrefix = "gruppe:";
private static readonly string FilePath = Path.Combine(AppSettings.Folder, "usage.json");
private static readonly Dictionary<string, UsageEntry> _data = Load();
private static DispatcherTimer? _saveTimer;
/// <summary>Etwas wurde gestartet (z. B. damit „Zuletzt benutzt“-Boxen sich aktualisieren).</summary>
public static event Action? Changed;
private static Dictionary<string, UsageEntry> Load()
{
try
{
if (File.Exists(FilePath) &&
JsonSerializer.Deserialize<Dictionary<string, UsageEntry>>(File.ReadAllText(FilePath)) is { } d)
return new(d, StringComparer.OrdinalIgnoreCase);
}
catch { }
return new(StringComparer.OrdinalIgnoreCase);
}
/// <summary>Programm/Datei starten und den Start mitzählen. Wirft bei Fehlern wie Process.Start.</summary>
public static void Start(string path)
{
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
Record(path);
}
public static void Record(string key)
{
if (!_data.TryGetValue(key, out var e)) _data[key] = e = new UsageEntry();
e.Count++;
e.Last = DateTime.Now;
ScheduleSave();
Changed?.Invoke();
}
public static int Count(string key) => _data.TryGetValue(key, out var e) ? e.Count : 0;
/// <summary>
/// Wie „beliebt“ etwas ist: Anzahl der Starts, mit der Zeit abnehmend gewichtet –
/// was du früher oft, aber seit Wochen nicht mehr benutzt hast, rutscht langsam nach hinten.
/// </summary>
public static double Score(string key)
{
if (!_data.TryGetValue(key, out var e)) return 0;
double days = Math.Max(0, (DateTime.Now - e.Last).TotalDays);
return e.Count / (1 + days / 30);
}
/// <summary>Zuletzt gestartete Dateien, die es noch gibt (neueste zuerst).</summary>
public static List<string> Recent(int max) => _data
.Where(kv => !kv.Key.StartsWith(GroupPrefix) && (File.Exists(kv.Key) || Directory.Exists(kv.Key)))
.OrderByDescending(kv => kv.Value.Last)
.Take(max)
.Select(kv => kv.Key)
.ToList();
/// <summary>Datei wurde umbenannt/verschoben → Zählung mitnehmen.</summary>
public static void Rename(string oldKey, string newKey)
{
if (!_data.Remove(oldKey, out var e)) return;
if (_data.TryGetValue(newKey, out var existing))
{
existing.Count += e.Count;
if (e.Last > existing.Last) existing.Last = e.Last;
}
else _data[newKey] = e;
ScheduleSave();
}
public static void Forget(string key)
{
if (_data.Remove(key)) { ScheduleSave(); Changed?.Invoke(); }
}
private static void ScheduleSave()
{
_saveTimer ??= new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) => Flush(),
Dispatcher.CurrentDispatcher);
_saveTimer.Stop();
_saveTimer.Start();
}
/// <summary>Sofort speichern (z. B. beim Beenden).</summary>
public static void Flush()
{
_saveTimer?.Stop();
try
{
// Nicht mehr vorhandene Einträge, die lange nicht benutzt wurden, aufräumen
foreach (var key in _data.Where(kv => kv.Value.Last < DateTime.Now.AddDays(-180) && !kv.Key.StartsWith(GroupPrefix)
&& !File.Exists(kv.Key) && !Directory.Exists(kv.Key)).Select(kv => kv.Key).ToList())
_data.Remove(key);
Directory.CreateDirectory(AppSettings.Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(_data));
}
catch { }
}
}
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {
"DeskCore/1.1.0": {
"DeskCore/1.0.3": {
"runtime": {
"DeskCore.dll": {}
}
@@ -14,7 +14,7 @@
}
},
"libraries": {
"DeskCore/1.1.0": {
"DeskCore/1.0.3": {
"type": "project",
"serviceable": false,
"sha512": ""
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
"targets": {
".NETCoreApp,Version=v9.0": {},
".NETCoreApp,Version=v9.0/win-x64": {
"DeskCore/1.0.2": {
"DeskCore/1.0.4": {
"runtime": {
"DeskCore.dll": {}
}
@@ -15,7 +15,7 @@
}
},
"libraries": {
"DeskCore/1.0.2": {
"DeskCore/1.0.4": {
"type": "project",
"serviceable": false,
"sha512": ""
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -5,7 +5,7 @@
},
"projects": {
"C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\DeskCore.csproj": {
"version": "1.0.2",
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\DeskCore.csproj",
"projectName": "DeskCore",
@@ -14,11 +14,11 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("Desktop-Icons in eigene Kategorien sortieren")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.1.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.1.0")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.3.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.3")]
[assembly: System.Reflection.AssemblyProductAttribute("DeskCore")]
[assembly: System.Reflection.AssemblyTitleAttribute("DeskCore")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.1.0.0")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.3.0")]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
@@ -1 +1 @@
d17168ced62c0a293e0650d70db6ef5e4d465299ab51ae40a71f8eeea14e9f11
bfd7fad03da13c19f8fcac283406c3821d18f304a78ff3c95dd91711cd51d003
@@ -1 +1 @@
995801f2cd094c1c8c14ed7c55bfffde38b7a854695e8926095786e44152a4e0
d5e3c6ce9ad890e87aa7c998a32fda8c32f01fe737caabe4f659dd00a01a4313
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,7 @@ TRACE;RELEASE;NET;NET9_0;NETCOREAPP;WINDOWS;WINDOWS7_0;NET5_0_OR_GREATER;NET6_0_
C:\Users\Viper\Desktop\Programme\DeskCore-Source\App.xaml
5-736700008
30-889519525
35182669225
210-1563322103
FenceWindow.xaml;MainWindow.xaml;Themes\Colors.Dark.xaml;Themes\Colors.Light.xaml;Themes\Controls.xaml;
Binary file not shown.
+18 -1
View File
@@ -1,4 +1,4 @@
#pragma checksum "..\..\..\FenceWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "0AD23B4AEE9336B299C37B05CB51A972E9704879"
#pragma checksum "..\..\..\FenceWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "9A6369B2C4E0DC49858C3B1BB4A30F2ACC93D65B"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
@@ -160,6 +160,14 @@ namespace DeskCore {
#line default
#line hidden
#line 90 "..\..\..\FenceWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Shapes.Rectangle DropMarker;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
@@ -255,6 +263,12 @@ namespace DeskCore {
#line default
#line hidden
#line 56 "..\..\..\FenceWindow.xaml"
this.Items.DragLeave += new System.Windows.DragEventHandler(this.Items_DragLeave);
#line default
#line hidden
#line 56 "..\..\..\FenceWindow.xaml"
this.Items.Drop += new System.Windows.DragEventHandler(this.Items_Drop);
@@ -297,6 +311,9 @@ namespace DeskCore {
case 16:
this.EmptyText = ((System.Windows.Controls.TextBlock)(target));
return;
case 17:
this.DropMarker = ((System.Windows.Shapes.Rectangle)(target));
return;
}
this._contentLoaded = true;
}
Binary file not shown.
+210 -140
View File
@@ -1,4 +1,4 @@
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "1FB67593B68374B113272765CC962A892CFA4924"
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "9B4C6A241407FC6E382D8BE231E67C4BF881195B"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
@@ -489,7 +489,39 @@ namespace DeskCore {
#line hidden
#line 654 "..\..\..\MainWindow.xaml"
#line 655 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox QuickSearchToggle;
#line default
#line hidden
#line 656 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox QuickSearchHotkeyBox;
#line default
#line hidden
#line 661 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock QuickSearchHint;
#line default
#line hidden
#line 664 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox QuickSearchStartMenuBox;
#line default
#line hidden
#line 671 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox FencesToggle;
@@ -497,7 +529,7 @@ namespace DeskCore {
#line hidden
#line 681 "..\..\..\MainWindow.xaml"
#line 698 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox DrawFencesToggle;
@@ -505,7 +537,7 @@ namespace DeskCore {
#line hidden
#line 702 "..\..\..\MainWindow.xaml"
#line 719 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock PagesHint;
@@ -513,7 +545,7 @@ namespace DeskCore {
#line hidden
#line 706 "..\..\..\MainWindow.xaml"
#line 723 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.ItemsControl PageList;
@@ -521,7 +553,7 @@ namespace DeskCore {
#line hidden
#line 728 "..\..\..\MainWindow.xaml"
#line 748 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox PageWheelBox;
@@ -529,7 +561,7 @@ namespace DeskCore {
#line hidden
#line 741 "..\..\..\MainWindow.xaml"
#line 761 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.WrapPanel DesktopTintSwatches;
@@ -537,7 +569,7 @@ namespace DeskCore {
#line hidden
#line 744 "..\..\..\MainWindow.xaml"
#line 764 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Slider DesktopTintStrength;
@@ -545,7 +577,7 @@ namespace DeskCore {
#line hidden
#line 751 "..\..\..\MainWindow.xaml"
#line 771 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox QuickHideToggle;
@@ -553,7 +585,7 @@ namespace DeskCore {
#line hidden
#line 762 "..\..\..\MainWindow.xaml"
#line 782 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox PeekToggle;
@@ -561,7 +593,7 @@ namespace DeskCore {
#line hidden
#line 766 "..\..\..\MainWindow.xaml"
#line 786 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock PeekHint;
@@ -569,7 +601,7 @@ namespace DeskCore {
#line hidden
#line 773 "..\..\..\MainWindow.xaml"
#line 793 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox ChameleonToggle;
@@ -577,7 +609,7 @@ namespace DeskCore {
#line hidden
#line 798 "..\..\..\MainWindow.xaml"
#line 818 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.ItemsControl SnapshotList;
@@ -585,7 +617,7 @@ namespace DeskCore {
#line hidden
#line 839 "..\..\..\MainWindow.xaml"
#line 859 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock BackupHint;
@@ -593,7 +625,7 @@ namespace DeskCore {
#line hidden
#line 848 "..\..\..\MainWindow.xaml"
#line 868 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button CheckUpdateButton;
@@ -601,7 +633,7 @@ namespace DeskCore {
#line hidden
#line 849 "..\..\..\MainWindow.xaml"
#line 869 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox AutoUpdateToggle;
@@ -609,7 +641,7 @@ namespace DeskCore {
#line hidden
#line 854 "..\..\..\MainWindow.xaml"
#line 874 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock UpdateStatusText;
@@ -617,7 +649,7 @@ namespace DeskCore {
#line hidden
#line 875 "..\..\..\MainWindow.xaml"
#line 895 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock AboutTitle;
@@ -625,7 +657,7 @@ namespace DeskCore {
#line hidden
#line 876 "..\..\..\MainWindow.xaml"
#line 896 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock AboutCopyright;
@@ -633,7 +665,7 @@ namespace DeskCore {
#line hidden
#line 877 "..\..\..\MainWindow.xaml"
#line 897 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock StoragePathText;
@@ -641,7 +673,7 @@ namespace DeskCore {
#line hidden
#line 892 "..\..\..\MainWindow.xaml"
#line 912 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border Toast;
@@ -649,7 +681,7 @@ namespace DeskCore {
#line hidden
#line 897 "..\..\..\MainWindow.xaml"
#line 917 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Media.TranslateTransform ToastShift;
@@ -657,7 +689,7 @@ namespace DeskCore {
#line hidden
#line 900 "..\..\..\MainWindow.xaml"
#line 920 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ToastBadge;
@@ -665,7 +697,7 @@ namespace DeskCore {
#line hidden
#line 901 "..\..\..\MainWindow.xaml"
#line 921 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ToastGlyph;
@@ -673,7 +705,7 @@ namespace DeskCore {
#line hidden
#line 904 "..\..\..\MainWindow.xaml"
#line 924 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ToastText;
@@ -681,7 +713,7 @@ namespace DeskCore {
#line hidden
#line 905 "..\..\..\MainWindow.xaml"
#line 925 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button ToastUndo;
@@ -689,7 +721,7 @@ namespace DeskCore {
#line hidden
#line 913 "..\..\..\MainWindow.xaml"
#line 933 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border DimOverlay;
@@ -697,7 +729,7 @@ namespace DeskCore {
#line hidden
#line 916 "..\..\..\MainWindow.xaml"
#line 936 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Grid ProgressOverlay;
@@ -705,7 +737,7 @@ namespace DeskCore {
#line hidden
#line 921 "..\..\..\MainWindow.xaml"
#line 941 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressTitle;
@@ -713,7 +745,7 @@ namespace DeskCore {
#line hidden
#line 922 "..\..\..\MainWindow.xaml"
#line 942 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressDetail;
@@ -721,7 +753,7 @@ namespace DeskCore {
#line hidden
#line 923 "..\..\..\MainWindow.xaml"
#line 943 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressTrack;
@@ -729,7 +761,7 @@ namespace DeskCore {
#line hidden
#line 924 "..\..\..\MainWindow.xaml"
#line 944 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressFill;
@@ -737,7 +769,7 @@ namespace DeskCore {
#line hidden
#line 927 "..\..\..\MainWindow.xaml"
#line 947 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button ProgressCancel;
@@ -745,7 +777,7 @@ namespace DeskCore {
#line hidden
#line 928 "..\..\..\MainWindow.xaml"
#line 948 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressStats;
@@ -1369,232 +1401,262 @@ namespace DeskCore {
#line hidden
return;
case 79:
this.FencesToggle = ((System.Windows.Controls.CheckBox)(target));
this.QuickSearchToggle = ((System.Windows.Controls.CheckBox)(target));
#line 654 "..\..\..\MainWindow.xaml"
this.FencesToggle.Click += new System.Windows.RoutedEventHandler(this.FencesToggle_Click);
#line 655 "..\..\..\MainWindow.xaml"
this.QuickSearchToggle.Click += new System.Windows.RoutedEventHandler(this.QuickSearchToggle_Click);
#line default
#line hidden
return;
case 80:
this.QuickSearchHotkeyBox = ((System.Windows.Controls.TextBox)(target));
#line 665 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.AddPortal_Click);
#line 657 "..\..\..\MainWindow.xaml"
this.QuickSearchHotkeyBox.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(this.QuickSearchHotkeyBox_PreviewKeyDown);
#line default
#line hidden
return;
case 81:
this.DrawFencesToggle = ((System.Windows.Controls.CheckBox)(target));
#line 681 "..\..\..\MainWindow.xaml"
this.DrawFencesToggle.Click += new System.Windows.RoutedEventHandler(this.DrawFencesToggle_Click);
#line default
#line hidden
this.QuickSearchHint = ((System.Windows.Controls.TextBlock)(target));
return;
case 82:
this.QuickSearchStartMenuBox = ((System.Windows.Controls.CheckBox)(target));
#line 693 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.AddPage_Click);
#line 664 "..\..\..\MainWindow.xaml"
this.QuickSearchStartMenuBox.Click += new System.Windows.RoutedEventHandler(this.QuickSearchStartMenu_Click);
#line default
#line hidden
return;
case 83:
this.PagesHint = ((System.Windows.Controls.TextBlock)(target));
this.FencesToggle = ((System.Windows.Controls.CheckBox)(target));
#line 671 "..\..\..\MainWindow.xaml"
this.FencesToggle.Click += new System.Windows.RoutedEventHandler(this.FencesToggle_Click);
#line default
#line hidden
return;
case 84:
this.PageList = ((System.Windows.Controls.ItemsControl)(target));
#line 682 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.AddPortal_Click);
#line default
#line hidden
return;
case 85:
this.DrawFencesToggle = ((System.Windows.Controls.CheckBox)(target));
#line 698 "..\..\..\MainWindow.xaml"
this.DrawFencesToggle.Click += new System.Windows.RoutedEventHandler(this.DrawFencesToggle_Click);
#line default
#line hidden
return;
case 86:
#line 710 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.AddPage_Click);
#line default
#line hidden
return;
case 87:
this.PagesHint = ((System.Windows.Controls.TextBlock)(target));
return;
case 88:
this.PageList = ((System.Windows.Controls.ItemsControl)(target));
return;
case 93:
this.PageWheelBox = ((System.Windows.Controls.CheckBox)(target));
#line 728 "..\..\..\MainWindow.xaml"
#line 748 "..\..\..\MainWindow.xaml"
this.PageWheelBox.Click += new System.Windows.RoutedEventHandler(this.PageWheel_Click);
#line default
#line hidden
return;
case 89:
case 94:
this.DesktopTintSwatches = ((System.Windows.Controls.WrapPanel)(target));
return;
case 90:
case 95:
this.DesktopTintStrength = ((System.Windows.Controls.Slider)(target));
#line 745 "..\..\..\MainWindow.xaml"
#line 765 "..\..\..\MainWindow.xaml"
this.DesktopTintStrength.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(this.DesktopTintStrength_ValueChanged);
#line default
#line hidden
return;
case 91:
case 96:
this.QuickHideToggle = ((System.Windows.Controls.CheckBox)(target));
#line 751 "..\..\..\MainWindow.xaml"
#line 771 "..\..\..\MainWindow.xaml"
this.QuickHideToggle.Click += new System.Windows.RoutedEventHandler(this.QuickHideToggle_Click);
#line default
#line hidden
return;
case 92:
case 97:
this.PeekToggle = ((System.Windows.Controls.CheckBox)(target));
#line 762 "..\..\..\MainWindow.xaml"
#line 782 "..\..\..\MainWindow.xaml"
this.PeekToggle.Click += new System.Windows.RoutedEventHandler(this.PeekToggle_Click);
#line default
#line hidden
return;
case 93:
case 98:
this.PeekHint = ((System.Windows.Controls.TextBlock)(target));
return;
case 94:
case 99:
this.ChameleonToggle = ((System.Windows.Controls.CheckBox)(target));
#line 773 "..\..\..\MainWindow.xaml"
#line 793 "..\..\..\MainWindow.xaml"
this.ChameleonToggle.Click += new System.Windows.RoutedEventHandler(this.ChameleonToggle_Click);
#line default
#line hidden
return;
case 95:
#line 785 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.SaveSnapshot_Click);
#line default
#line hidden
return;
case 96:
this.SnapshotList = ((System.Windows.Controls.ItemsControl)(target));
return;
case 99:
#line 823 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Export_Click);
#line default
#line hidden
return;
case 100:
#line 829 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Import_Click);
#line 805 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.SaveSnapshot_Click);
#line default
#line hidden
return;
case 101:
this.BackupHint = ((System.Windows.Controls.TextBlock)(target));
return;
case 102:
this.CheckUpdateButton = ((System.Windows.Controls.Button)(target));
#line 848 "..\..\..\MainWindow.xaml"
this.CheckUpdateButton.Click += new System.Windows.RoutedEventHandler(this.CheckUpdate_Click);
#line default
#line hidden
return;
case 103:
this.AutoUpdateToggle = ((System.Windows.Controls.CheckBox)(target));
#line 849 "..\..\..\MainWindow.xaml"
this.AutoUpdateToggle.Click += new System.Windows.RoutedEventHandler(this.AutoUpdateToggle_Click);
#line default
#line hidden
this.SnapshotList = ((System.Windows.Controls.ItemsControl)(target));
return;
case 104:
this.UpdateStatusText = ((System.Windows.Controls.TextBlock)(target));
#line 843 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Export_Click);
#line default
#line hidden
return;
case 105:
#line 864 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.License_Click);
#line 849 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Import_Click);
#line default
#line hidden
return;
case 106:
#line 865 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.OpenStorage_Click);
#line default
#line hidden
this.BackupHint = ((System.Windows.Controls.TextBlock)(target));
return;
case 107:
this.CheckUpdateButton = ((System.Windows.Controls.Button)(target));
#line 866 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Quit_Click);
#line 868 "..\..\..\MainWindow.xaml"
this.CheckUpdateButton.Click += new System.Windows.RoutedEventHandler(this.CheckUpdate_Click);
#line default
#line hidden
return;
case 108:
this.AboutTitle = ((System.Windows.Controls.TextBlock)(target));
this.AutoUpdateToggle = ((System.Windows.Controls.CheckBox)(target));
#line 869 "..\..\..\MainWindow.xaml"
this.AutoUpdateToggle.Click += new System.Windows.RoutedEventHandler(this.AutoUpdateToggle_Click);
#line default
#line hidden
return;
case 109:
this.AboutCopyright = ((System.Windows.Controls.TextBlock)(target));
this.UpdateStatusText = ((System.Windows.Controls.TextBlock)(target));
return;
case 110:
this.StoragePathText = ((System.Windows.Controls.TextBlock)(target));
#line 884 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.License_Click);
#line default
#line hidden
return;
case 111:
this.Toast = ((System.Windows.Controls.Border)(target));
#line 885 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.OpenStorage_Click);
#line default
#line hidden
return;
case 112:
this.ToastShift = ((System.Windows.Media.TranslateTransform)(target));
#line 886 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Quit_Click);
#line default
#line hidden
return;
case 113:
this.ToastBadge = ((System.Windows.Controls.Border)(target));
this.AboutTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 114:
this.ToastGlyph = ((System.Windows.Controls.TextBlock)(target));
this.AboutCopyright = ((System.Windows.Controls.TextBlock)(target));
return;
case 115:
this.ToastText = ((System.Windows.Controls.TextBlock)(target));
this.StoragePathText = ((System.Windows.Controls.TextBlock)(target));
return;
case 116:
this.Toast = ((System.Windows.Controls.Border)(target));
return;
case 117:
this.ToastShift = ((System.Windows.Media.TranslateTransform)(target));
return;
case 118:
this.ToastBadge = ((System.Windows.Controls.Border)(target));
return;
case 119:
this.ToastGlyph = ((System.Windows.Controls.TextBlock)(target));
return;
case 120:
this.ToastText = ((System.Windows.Controls.TextBlock)(target));
return;
case 121:
this.ToastUndo = ((System.Windows.Controls.Button)(target));
#line 906 "..\..\..\MainWindow.xaml"
#line 926 "..\..\..\MainWindow.xaml"
this.ToastUndo.Click += new System.Windows.RoutedEventHandler(this.Undo_Click);
#line default
#line hidden
return;
case 117:
case 122:
this.DimOverlay = ((System.Windows.Controls.Border)(target));
return;
case 118:
case 123:
this.ProgressOverlay = ((System.Windows.Controls.Grid)(target));
return;
case 119:
case 124:
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 120:
case 125:
this.ProgressDetail = ((System.Windows.Controls.TextBlock)(target));
return;
case 121:
case 126:
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
return;
case 122:
case 127:
this.ProgressFill = ((System.Windows.Controls.Border)(target));
return;
case 123:
case 128:
this.ProgressCancel = ((System.Windows.Controls.Button)(target));
#line 927 "..\..\..\MainWindow.xaml"
#line 947 "..\..\..\MainWindow.xaml"
this.ProgressCancel.Click += new System.Windows.RoutedEventHandler(this.ProgressCancel_Click);
#line default
#line hidden
return;
case 124:
case 129:
this.ProgressStats = ((System.Windows.Controls.TextBlock)(target));
return;
}
@@ -1650,41 +1712,49 @@ namespace DeskCore {
#line default
#line hidden
break;
case 85:
case 89:
#line 711 "..\..\..\MainWindow.xaml"
#line 728 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.ShowPage_Click);
#line default
#line hidden
break;
case 86:
case 90:
#line 712 "..\..\..\MainWindow.xaml"
#line 729 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.PageSchedule_Click);
#line default
#line hidden
break;
case 91:
#line 732 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.RenamePage_Click);
#line default
#line hidden
break;
case 87:
case 92:
#line 715 "..\..\..\MainWindow.xaml"
#line 735 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.DeletePage_Click);
#line default
#line hidden
break;
case 97:
case 102:
#line 803 "..\..\..\MainWindow.xaml"
#line 823 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.RestoreSnapshot_Click);
#line default
#line hidden
break;
case 98:
case 103:
#line 804 "..\..\..\MainWindow.xaml"
#line 824 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.DeleteSnapshot_Click);
#line default
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,11 +14,11 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("Desktop-Icons in eigene Kategorien sortieren")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.2.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.2")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.4.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.4")]
[assembly: System.Reflection.AssemblyProductAttribute("DeskCore")]
[assembly: System.Reflection.AssemblyTitleAttribute("DeskCore")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.2.0")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.4.0")]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
@@ -1 +1 @@
57d1ccc33fdcba0e29478e1e9cfa15f4a340b4111bbb0492449414f7bc98a8df
bff3fcaffba3059df2921f8581df504054cd37089362a5f672ae39adae9a7f45
@@ -1 +1 @@
57da6822c263f8f820c2ffab0b10f4d5d307e352f24dd2ab1be6cecf0650758a
5a376d0097574aa1add0b0fb5cf10733829a2cece610b4c81c5b32a7613b90f3
@@ -7,7 +7,7 @@
"targets": {
".NETCoreApp,Version=v9.0": {},
".NETCoreApp,Version=v9.0/win-x64": {
"DeskCore/1.0.2": {
"DeskCore/1.0.4": {
"runtime": {
"DeskCore.dll": {}
}
@@ -15,7 +15,7 @@
}
},
"libraries": {
"DeskCore/1.0.2": {
"DeskCore/1.0.4": {
"type": "project",
"serviceable": false,
"sha512": ""
Binary file not shown.
@@ -1 +1 @@
4646ec2c3128e24bd804c9b6613a33d9861c314cc70353840d6e8baa99cac6a1
ceac0ba481d934c5e5ba3b309ea630ab4600f4e240c6500a6733eb2896d028a0
@@ -12,7 +12,7 @@ TRACE;RELEASE;NET;NET9_0;NETCOREAPP;WINDOWS;WINDOWS7_0;NET5_0_OR_GREATER;NET6_0_
C:\Users\Viper\Desktop\Programme\DeskCore-Source\App.xaml
5-736700008
30-272641797
35799546953
210-1563322103
FenceWindow.xaml;MainWindow.xaml;Themes\Colors.Dark.xaml;Themes\Colors.Light.xaml;Themes\Controls.xaml;
Binary file not shown.
@@ -1,4 +1,4 @@
#pragma checksum "..\..\..\..\FenceWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "0AD23B4AEE9336B299C37B05CB51A972E9704879"
#pragma checksum "..\..\..\..\FenceWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "9A6369B2C4E0DC49858C3B1BB4A30F2ACC93D65B"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
@@ -160,6 +160,14 @@ namespace DeskCore {
#line default
#line hidden
#line 90 "..\..\..\..\FenceWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Shapes.Rectangle DropMarker;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
@@ -255,6 +263,12 @@ namespace DeskCore {
#line default
#line hidden
#line 56 "..\..\..\..\FenceWindow.xaml"
this.Items.DragLeave += new System.Windows.DragEventHandler(this.Items_DragLeave);
#line default
#line hidden
#line 56 "..\..\..\..\FenceWindow.xaml"
this.Items.Drop += new System.Windows.DragEventHandler(this.Items_Drop);
@@ -297,6 +311,9 @@ namespace DeskCore {
case 16:
this.EmptyText = ((System.Windows.Controls.TextBlock)(target));
return;
case 17:
this.DropMarker = ((System.Windows.Shapes.Rectangle)(target));
return;
}
this._contentLoaded = true;
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -80,7 +80,7 @@
"C:\\Users\\Viper\\.nuget\\packages\\": {}
},
"project": {
"version": "1.0.2",
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\DeskCore.csproj",
"projectName": "DeskCore",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"version": 2,
"dgSpecHash": "pBG5Z3T7iF8=",
"dgSpecHash": "biUO9bjeZCI=",
"success": true,
"projectFilePath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\DeskCore.csproj",
"expectedPackageFiles": [