├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ ├── config.yml
│ └── feature_request.yaml
└── workflows
│ └── library.nuget.yml
├── .gitignore
├── LICENSE
├── README.md
├── Settings.XamlStyler
├── Wpf.Ui.Violeta.sln
├── branding
├── geometric_splash.psd
├── microsoft-fluent-resources.psd
├── wpfui.ico
├── wpfui.png
├── wpfui.psd
└── wpfui_full.png
└── src
├── Wpf.Ui.Test
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── GlobalUsing.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Resources
│ ├── Images
│ │ ├── ProfilePicture.ico
│ │ └── Tiara.png
│ └── Strings
│ │ └── Sample.md
├── TrayIconManager.cs
└── Wpf.Ui.Test.csproj
└── Wpf.Ui.Violeta
├── Appearance
├── ResourceDictionaryManager.cs
├── SystemMenuThemeManager.cs
└── ThemeManager.cs
├── AssemblyInfo.cs
├── Controls
├── BitmapIcon
│ └── BitmapIcon.cs
├── Button
│ └── Button.xaml
├── CachedImage
│ ├── CachedImage.cs
│ └── FileCacheForImage.cs
├── ContentDialog
│ ├── ContentDialog.cs
│ ├── ContentDialog.xaml
│ ├── ContentDialogButton.cs
│ ├── ContentDialogButtonClickDeferral.cs
│ ├── ContentDialogButtonClickEventArgs.cs
│ ├── ContentDialogClosedEventArgs.cs
│ ├── ContentDialogClosingDeferral.cs
│ ├── ContentDialogClosingEventArgs.cs
│ ├── ContentDialogHostService.cs
│ ├── ContentDialogOpenedEventArgs.cs
│ ├── ContentDialogPlacement.cs
│ ├── ContentDialogResult.cs
│ └── CornerRadiusFilterConverter.cs
├── DataGrid
│ ├── DataGrid.xaml
│ └── LegacyDataGrid.xaml
├── ExceptionReport
│ ├── ExceptionReport.cs
│ ├── ExceptionWindow.xaml
│ └── ExceptionWindow.xaml.cs
├── Expander
│ └── Expander.xaml
├── Flyout
│ └── FlyoutService.cs
├── FontIcon
│ ├── CustomFontSymbols.cs
│ ├── FontIconFallback.cs
│ ├── FontIconFallback.xaml
│ └── FontSymbols.cs
├── ImageView
│ ├── AnimatedImage.cs
│ ├── AnimationProvider.cs
│ ├── BitmapImageProvider.cs
│ ├── ImageView.xaml
│ └── ImageView.xaml.cs
├── Layout
│ ├── AutoGrid.cs
│ ├── Border.cs
│ ├── Grid.cs
│ ├── StackPanel.cs
│ └── WrapPanel.cs
├── MessageBox
│ ├── MessageBox.Image.cs
│ ├── MessageBox.cs
│ ├── MessageBoxButtonClickDeferral.cs
│ ├── MessageBoxButtonClickEventArgs.cs
│ ├── MessageBoxClosedEventArgs.cs
│ ├── MessageBoxClosingDeferral.cs
│ ├── MessageBoxClosingEventArgs.cs
│ ├── MessageBoxContentTemplateSelector.cs
│ ├── MessageBoxDialog.cs
│ ├── MessageBoxDialog.xaml
│ ├── MessageBoxIcon.cs
│ ├── MessageBoxIconConverter.cs
│ ├── MessageBoxIconExtensions.cs
│ ├── MessageBoxIconForegroundConverter.cs
│ ├── MessageBoxImageExtensions.cs
│ ├── MessageBoxOpenedEventArgs.cs
│ ├── MessageBoxSymbolGlyph.cs
│ └── MessageBoxTemplateSettings.cs
├── PendingBox
│ ├── IPendingHandler.cs
│ ├── Loading.cs
│ ├── Loading.xaml
│ ├── PendingBox.cs
│ ├── PendingBoxDialog.cs
│ ├── PendingBoxDialog.xaml
│ └── PendingHandler.cs
├── PersonPicture
│ ├── InitialsGenerator.cs
│ ├── PersonPicture.cs
│ ├── PersonPicture.properties.cs
│ ├── PersonPicture.xaml
│ ├── PersonPictureAutomationPeer.cs
│ └── PersonPictureTemplateSettings.cs
├── Primitives
│ ├── ContentPresenterEx.cs
│ ├── CustomPopupPlacementHelper.cs
│ ├── IconElementEx.cs
│ ├── MenuItemGroup.cs
│ ├── PopupPositioner.cs
│ ├── RadioButtonGroup.cs
│ ├── SimpleVisualStateManager.cs
│ ├── ThemeShadowChrome.cs
│ ├── ToggleButtonGroup.cs
│ ├── VisualStateGroupListener.cs
│ └── VisualTree.cs
├── SmoothScrollViewer
│ ├── SmoothScrollViewer.cs
│ └── SmoothScrollViewer.xaml
├── Splash
│ ├── Splash.cs
│ ├── SplashConfig.cs
│ ├── SplashWindow.xaml
│ └── SplashWindow.xaml.cs
├── TextBox
│ └── TextBox.xaml
├── Toast
│ ├── Toast.cs
│ ├── ToastConfig.cs
│ ├── ToastControl.xaml
│ ├── ToastControl.xaml.cs
│ ├── ToastIcon.cs
│ ├── ToastIconConverter.cs
│ ├── ToastIconForegroundConverter.cs
│ ├── ToastIconVisibilityConverter.cs
│ └── ToastLocation.cs
├── TreeListView
│ ├── TreeLevelToIndentConverter.cs
│ ├── TreeListView.cs
│ ├── TreeListView.xaml
│ ├── TreeListViewItem.cs
│ └── TreeRowExpander.cs
└── TreeModelListView
│ ├── ITreeModel.cs
│ ├── TreeModelCanExpandConverter.cs
│ ├── TreeModelCollection{T}.cs
│ ├── TreeModelLevelToIndentConverter.cs
│ ├── TreeModelListView.cs
│ ├── TreeModelListView.xaml
│ ├── TreeModelListViewItem.cs
│ ├── TreeModelNode.cs
│ ├── TreeModelObject{T}.cs
│ ├── TreeModelRowCollection{T}.cs
│ └── TreeModelRowExpander.cs
├── Converters
├── FileSizeStringConverter.cs
└── PathToIconConverter.cs
├── GlobalSuppressions.cs
├── Markup
├── ControlsDictionary.cs
├── StaticResourceExtension.cs
└── ThemesDictionary.cs
├── Resources
├── Fonts
│ ├── Segoe Fluent Icons.ttf
│ └── Violeta Fluent Icons.ttf
├── Images
│ ├── background-b.png
│ └── background.png
├── Localization
│ ├── SH.Designer.cs
│ ├── SH.en.resx
│ ├── SH.fr.resx
│ ├── SH.id.resx
│ ├── SH.ja.resx
│ ├── SH.ko.resx
│ ├── SH.pt.resx
│ ├── SH.resx
│ ├── SH.ru.resx
│ ├── SH.vi.resx
│ └── SH.zh-Hant.resx
├── ManifestResourceProvider.cs
├── ResourcesProvider.cs
├── Theme
│ ├── Dark.xaml
│ └── Light.xaml
├── Variables.xaml
└── Wpf.Ui.xaml
├── Threading
├── ApplicationDispatcher.cs
└── STAThread.cs
├── Win32
├── DpiAware.cs
├── DpiHelper.cs
├── Gdi32.cs
├── IconManager.cs
├── NTdll.cs
├── SHCore.cs
├── Shell32.cs
├── Structs
│ ├── POINT.cs
│ └── RECT.cs
├── TrayIconHost.cs
├── User32.cs
└── UxTheme.cs
└── Wpf.Ui.Violeta.csproj
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Create a report to help us improve
3 | title: "Bug title"
4 | labels: [bug]
5 | body:
6 | - type: textarea
7 | validations:
8 | required: true
9 | attributes:
10 | label: Describe the bug
11 | description: A clear and concise description of what the bug is
12 | - type: textarea
13 | validations:
14 | required: true
15 | attributes:
16 | label: To Reproduce
17 | description: Steps to reproduce the behavior
18 | - type: textarea
19 | validations:
20 | required: true
21 | attributes:
22 | label: Expected behavior
23 | description: A clear and concise description of what you expected to happen
24 | - type: textarea
25 | attributes:
26 | label: Screenshots
27 | description: If applicable, add screenshots to help explain your problem
28 | - type: textarea
29 | validations:
30 | required: true
31 | attributes:
32 | label: OS version
33 | description: Which OS versions did you see the issue on?
34 | - type: textarea
35 | validations:
36 | required: true
37 | attributes:
38 | label: .NET version
39 | description: Which .NET versions did you see the issue on?
40 | - type: textarea
41 | validations:
42 | required: true
43 | attributes:
44 | label: WPF-UI NuGet version
45 | description: Which WPF-UI NuGet versions did you see the issue on?
46 | - type: textarea
47 | attributes:
48 | label: Additional context
49 | description: Add any other context about the problem here
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Documentation
4 | url: https://wpfui.lepo.co/documentation/
5 | about: Find out how WPF UI works.
6 | - name: Tutorial
7 | url: https://wpfui.lepo.co/documentation/tutorial
8 | about: Having trouble with the basics? Check out the tutorial!
9 | - name: lepo.co contact
10 | url: https://lepo.co/contact
11 | about: Do you want to establish business contact? Let us know.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for the wpfui
3 | title: "Feature request title"
4 | labels: [enhancement]
5 | body:
6 | - type: textarea
7 | validations:
8 | required: true
9 | attributes:
10 | label: Is your feature request related to a problem? Please describe
11 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | - type: textarea
13 | validations:
14 | required: true
15 | attributes:
16 | label: Describe the solution you'd like
17 | description: A clear and concise description of what you want to happen
18 | - type: textarea
19 | attributes:
20 | label: Describe alternatives you've considered
21 | description: A clear and concise description of any alternative solutions or features you've considered
22 | - type: textarea
23 | attributes:
24 | label: Additional context
25 | description: Add any other context or screenshots about the feature request here
--------------------------------------------------------------------------------
/.github/workflows/library.nuget.yml:
--------------------------------------------------------------------------------
1 | name: wpfui.violeta
2 |
3 | on:
4 | release:
5 | types: [created]
6 | push:
7 | branches:
8 | - master
9 |
10 | jobs:
11 | build:
12 | runs-on: windows-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Setup .NET
18 | uses: actions/setup-dotnet@v4
19 | with:
20 | dotnet-version: 9.0.x
21 |
22 | - name: Setup Windows SDK
23 | uses: GuillaumeFalourd/setup-windows10-sdk-action@v1.5
24 |
25 | - name: Install dependencies
26 | run: dotnet restore
27 |
28 | - name: Build
29 | run: dotnet build --configuration Release --no-restore
30 |
31 | - name: Pack
32 | run: dotnet pack src\Wpf.Ui.Violeta\Wpf.Ui.Violeta.csproj -c Release --no-build -o out
33 |
34 | - name: Push NuGet package
35 | # run: dotnet nuget push "out\*.nupkg" -k ${{secrets.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json
36 | uses: actions/upload-artifact@v4
37 | with:
38 | name: nuget-packages
39 | path: out/*.nupkg
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 ema
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Settings.XamlStyler:
--------------------------------------------------------------------------------
1 | {
2 | "AttributesTolerance": 2,
3 | "KeepFirstAttributeOnSameLine": false,
4 | "MaxAttributeCharactersPerLine": 0,
5 | "MaxAttributesPerLine": 1,
6 | "NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
7 | "SeparateByGroups": false,
8 | "AttributeIndentation": 0,
9 | "AttributeIndentationStyle": 1,
10 | "RemoveDesignTimeReferences": false,
11 | "IgnoreDesignTimeReferencePrefix": false,
12 | "EnableAttributeReordering": true,
13 | "AttributeOrderingRuleGroups": [
14 | "x:Class",
15 | "xmlns, xmlns:x",
16 | "xmlns:*",
17 | "x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
18 | "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
19 | "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
20 | "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
21 | "*:*, *",
22 | "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
23 | "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
24 | "Storyboard.*, From, To, Duration"
25 | ],
26 | "FirstLineAttributes": "",
27 | "OrderAttributesByName": true,
28 | "PutEndingBracketOnNewLine": false,
29 | "RemoveEndingTagOfEmptyElement": true,
30 | "SpaceBeforeClosingSlash": true,
31 | "RootElementLineBreakRule": 0,
32 | "ReorderVSM": 2,
33 | "ReorderGridChildren": false,
34 | "ReorderCanvasChildren": false,
35 | "ReorderSetters": 0,
36 | "FormatMarkupExtension": true,
37 | "NoNewLineMarkupExtensions": "x:Bind, Binding",
38 | "ThicknessSeparator": 2,
39 | "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
40 | "FormatOnSave": true,
41 | "CommentPadding": 2,
42 | "IndentSize": 4,
43 | "IndentWithTabs": false
44 | }
--------------------------------------------------------------------------------
/Wpf.Ui.Violeta.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34902.65
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wpf.Ui.Test", "src\Wpf.Ui.Test\Wpf.Ui.Test.csproj", "{DB963C29-774D-4E63-A6C0-BEE52A23E40B}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wpf.Ui.Violeta", "src\Wpf.Ui.Violeta\Wpf.Ui.Violeta.csproj", "{2DBA3326-F339-48CB-BCCC-F2C4FBF2E612}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {DB963C29-774D-4E63-A6C0-BEE52A23E40B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {DB963C29-774D-4E63-A6C0-BEE52A23E40B}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {DB963C29-774D-4E63-A6C0-BEE52A23E40B}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {DB963C29-774D-4E63-A6C0-BEE52A23E40B}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {2DBA3326-F339-48CB-BCCC-F2C4FBF2E612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {2DBA3326-F339-48CB-BCCC-F2C4FBF2E612}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {2DBA3326-F339-48CB-BCCC-F2C4FBF2E612}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {2DBA3326-F339-48CB-BCCC-F2C4FBF2E612}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {0BEEA15F-4DFC-4E8B-B5B6-2E2DECE1E579}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/branding/geometric_splash.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/geometric_splash.psd
--------------------------------------------------------------------------------
/branding/microsoft-fluent-resources.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/microsoft-fluent-resources.psd
--------------------------------------------------------------------------------
/branding/wpfui.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/wpfui.ico
--------------------------------------------------------------------------------
/branding/wpfui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/wpfui.png
--------------------------------------------------------------------------------
/branding/wpfui.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/wpfui.psd
--------------------------------------------------------------------------------
/branding/wpfui_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/branding/wpfui_full.png
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/App.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using System.Windows.Threading;
4 | using Wpf.Ui.Violeta.Appearance;
5 | using Wpf.Ui.Violeta.Controls;
6 | using Wpf.Ui.Violeta.Resources;
7 |
8 | namespace Wpf.Ui.Test;
9 |
10 | public partial class App : Application
11 | {
12 | public App()
13 | {
14 | SystemMenuThemeManager.Apply();
15 | TrayIconManager.Start();
16 | Splash.ShowAsync("pack://application:,,,/Wpf.Ui.Test;component/wpfui.png", 0.98d);
17 | InitializeComponent();
18 |
19 | DispatcherUnhandledException += (object s, DispatcherUnhandledExceptionEventArgs e) =>
20 | {
21 | Debug.WriteLine("Application.DispatcherUnhandledException " + e.Exception?.ToString() ?? string.Empty);
22 | ExceptionReport.Show(e.Exception!);
23 | e.Handled = true;
24 | };
25 |
26 | string sampleMd = ResourcesProvider.GetString(@"pack://application:,,,/Resources/Strings/Sample.md");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/GlobalUsing.cs:
--------------------------------------------------------------------------------
1 | global using MessageBox = Wpf.Ui.Violeta.Controls.MessageBox;
2 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/Resources/Images/ProfilePicture.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/src/Wpf.Ui.Test/Resources/Images/ProfilePicture.ico
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/Resources/Images/Tiara.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emako/wpfui.violeta/75b03bfa4615e0cddf133952c97be5db52596382/src/Wpf.Ui.Test/Resources/Images/Tiara.png
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/Resources/Strings/Sample.md:
--------------------------------------------------------------------------------
1 | # Sample
2 |
3 | ## H2
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/TrayIconManager.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using CommunityToolkit.Mvvm.Input;
3 | using System;
4 | using System.ComponentModel;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Reflection;
8 | using System.Runtime.InteropServices;
9 | using System.Windows;
10 | using Wpf.Ui.Violeta.Resources;
11 | using Wpf.Ui.Violeta.Win32;
12 |
13 | namespace Wpf.Ui.Test;
14 |
15 | internal partial class TrayIconManager
16 | {
17 | private static TrayIconManager _instance = null!;
18 |
19 | private readonly TrayIconHost? _iconHost = null;
20 |
21 | private TrayIconManager()
22 | {
23 | using Win32Icon icon = new(ResourcesProvider.GetStream(new Uri("pack://application:,,,/Wpf.Ui.Test;component/Resources/Images/ProfilePicture.ico")));
24 |
25 | _iconHost = new TrayIconHost()
26 | {
27 | ToolTipText = "Wpf.Ui.Test",
28 | Icon = icon.Handle,
29 | Menu =
30 | [
31 | new TrayMenuItem()
32 | {
33 | Header = Version,
34 | IsEnabled = false,
35 | },
36 | new TraySeparator(),
37 | new TrayMenuItem()
38 | {
39 | Header = "Restart",
40 | Command = RestartCommand,
41 | },
42 | new TrayMenuItem()
43 | {
44 | Header = "Exit",
45 | Command = ExitCommand,
46 | }
47 | ],
48 | };
49 |
50 | _iconHost.LeftDoubleClick += (_, _) => ActivateOrRestoreMainWindow();
51 | }
52 |
53 | public static TrayIconManager GetInstance()
54 | {
55 | return _instance ??= new TrayIconManager();
56 | }
57 |
58 | public static void Start()
59 | {
60 | _ = GetInstance();
61 | }
62 | }
63 |
64 | internal partial class TrayIconManager : ObservableObject
65 | {
66 | [ObservableProperty]
67 | private string version = $"v{Assembly.GetExecutingAssembly().GetName().Version!.ToString(4)}";
68 |
69 | [RelayCommand]
70 | private void ActivateOrRestoreMainWindow()
71 | {
72 | if (Application.Current.MainWindow is not null)
73 | {
74 | if (Application.Current.MainWindow.IsVisible)
75 | {
76 | Application.Current.MainWindow.Hide();
77 | }
78 | else
79 | {
80 | Application.Current.MainWindow.Show();
81 | Application.Current.MainWindow.Activate();
82 | }
83 | }
84 | }
85 |
86 | [RelayCommand]
87 | private void Restart()
88 | {
89 | try
90 | {
91 | using Process process = new()
92 | {
93 | StartInfo = new ProcessStartInfo()
94 | {
95 | FileName = GetExecutablePath(),
96 | WorkingDirectory = Environment.CurrentDirectory,
97 | UseShellExecute = true,
98 | },
99 | };
100 | process.Start();
101 | }
102 | catch (Win32Exception)
103 | {
104 | return;
105 | }
106 |
107 | Process.GetCurrentProcess().Kill();
108 |
109 | static string GetExecutablePath()
110 | {
111 | string fileName = AppDomain.CurrentDomain.FriendlyName;
112 |
113 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
114 | fileName += ".exe";
115 |
116 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
117 | }
118 | }
119 |
120 | [RelayCommand]
121 | private void Exit()
122 | {
123 | Application.Current.Shutdown();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Test/Wpf.Ui.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net9.0-windows
6 | enable
7 | true
8 | latest
9 | 4.0.2.3
10 | 4.0.2.3
11 | $(VersionPrefix)4.0.2.3
12 | Resources\Images\ProfilePicture.ico
13 | embedded
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Appearance/ResourceDictionaryManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Windows;
4 |
5 | namespace Wpf.Ui.Violeta.Appearance;
6 |
7 | internal class ResourceDictionaryManager
8 | {
9 | public string SearchNamespace { get; }
10 |
11 | public ResourceDictionaryManager(string searchNamespace)
12 | {
13 | SearchNamespace = searchNamespace;
14 | }
15 |
16 | public bool HasDictionary(string resourceLookup)
17 | {
18 | return GetDictionary(resourceLookup) != null;
19 | }
20 |
21 | public ResourceDictionary? GetDictionary(string resourceLookup)
22 | {
23 | Collection applicationDictionaries = GetApplicationMergedDictionaries();
24 |
25 | if (applicationDictionaries.Count == 0)
26 | {
27 | return null;
28 | }
29 |
30 | resourceLookup = resourceLookup.ToLower().Trim();
31 |
32 | foreach (ResourceDictionary t in applicationDictionaries)
33 | {
34 | string resourceDictionaryUri;
35 |
36 | if (t?.Source != null)
37 | {
38 | resourceDictionaryUri = t.Source.ToString().ToLower().Trim();
39 |
40 | if (
41 | resourceDictionaryUri.Contains(SearchNamespace)
42 | && resourceDictionaryUri.Contains(resourceLookup)
43 | )
44 | {
45 | return t;
46 | }
47 | }
48 |
49 | foreach (ResourceDictionary? t1 in t!.MergedDictionaries)
50 | {
51 | if (t1?.Source == null)
52 | {
53 | continue;
54 | }
55 |
56 | resourceDictionaryUri = t1.Source.ToString().ToLower().Trim();
57 |
58 | if (
59 | !resourceDictionaryUri.Contains(SearchNamespace)
60 | || !resourceDictionaryUri.Contains(resourceLookup)
61 | )
62 | {
63 | continue;
64 | }
65 |
66 | return t1;
67 | }
68 | }
69 |
70 | return null;
71 | }
72 |
73 | public bool UpdateDictionary(string resourceLookup, Uri? newResourceUri)
74 | {
75 | Collection applicationDictionaries = UiApplication
76 | .Current
77 | .Resources
78 | .MergedDictionaries;
79 |
80 | if (applicationDictionaries.Count == 0 || newResourceUri is null)
81 | {
82 | return false;
83 | }
84 |
85 | resourceLookup = resourceLookup.ToLower().Trim();
86 |
87 | for (var i = 0; i < applicationDictionaries.Count; i++)
88 | {
89 | string sourceUri;
90 |
91 | if (applicationDictionaries[i]?.Source != null)
92 | {
93 | sourceUri = applicationDictionaries[i].Source.ToString().ToLower().Trim();
94 |
95 | if (sourceUri.Contains(SearchNamespace) && sourceUri.Contains(resourceLookup))
96 | {
97 | applicationDictionaries[i] = new() { Source = newResourceUri };
98 |
99 | return true;
100 | }
101 | }
102 |
103 | for (var j = 0; j < applicationDictionaries[i].MergedDictionaries.Count; j++)
104 | {
105 | if (applicationDictionaries[i].MergedDictionaries[j]?.Source == null)
106 | {
107 | continue;
108 | }
109 |
110 | sourceUri = applicationDictionaries[i]
111 | .MergedDictionaries[j]
112 | .Source.ToString()
113 | .ToLower()
114 | .Trim();
115 |
116 | if (!sourceUri.Contains(SearchNamespace) || !sourceUri.Contains(resourceLookup))
117 | {
118 | continue;
119 | }
120 |
121 | applicationDictionaries[i].MergedDictionaries[j] = new() { Source = newResourceUri };
122 |
123 | return true;
124 | }
125 | }
126 |
127 | return false;
128 | }
129 |
130 | private Collection GetApplicationMergedDictionaries()
131 | {
132 | return UiApplication.Current.Resources.MergedDictionaries;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Appearance/SystemMenuThemeManager.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using Wpf.Ui.Appearance;
4 | using Wpf.Ui.Violeta.Win32;
5 |
6 | namespace Wpf.Ui.Violeta.Appearance;
7 |
8 | public static class SystemMenuThemeManager
9 | {
10 | public static void Apply(SystemMenuTheme theme = SystemMenuTheme.Auto)
11 | {
12 | // Enable dark mode for context menus if using dark theme
13 | if (Environment.OSVersion.Version.Build >= 18362) // Windows 10 1903
14 | {
15 | if (theme == SystemMenuTheme.Auto)
16 | {
17 | // UxTheme methods will apply all of menus.
18 | // However, the Windows style system prefers that
19 | // Windows System Menu is based on `Apps Theme`,
20 | // and Tray Context Menu is based on `System Theme` when using a custom theme.
21 | // But actually we can't have our cake and eat it too.
22 | // Finally, we synchronize the theme styles of tray with higher usage rates.
23 | if (ThemeManager.GetSystemTheme() == SystemTheme.Dark)
24 | {
25 | _ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
26 | UxTheme.FlushMenuThemes();
27 | }
28 |
29 | // Synchronize the theme with system settings
30 | SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
31 | SystemEvents.UserPreferenceChanged += OnUserPreferenceChangedEventHandler;
32 | }
33 | else if (theme == SystemMenuTheme.Dark)
34 | {
35 | SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
36 | _ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
37 | UxTheme.FlushMenuThemes();
38 | }
39 | else if (theme == SystemMenuTheme.Light)
40 | {
41 | SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
42 | _ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceLight);
43 | UxTheme.FlushMenuThemes();
44 | }
45 | }
46 | }
47 |
48 | private static void OnUserPreferenceChangedEventHandler(object sender, UserPreferenceChangedEventArgs e)
49 | {
50 | if (ThemeManager.GetSystemTheme() == SystemTheme.Dark)
51 | {
52 | _ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
53 | UxTheme.FlushMenuThemes();
54 | }
55 | else
56 | {
57 | _ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceLight);
58 | UxTheme.FlushMenuThemes();
59 | }
60 | }
61 | }
62 |
63 | ///
64 | /// Theme in which an system menu is displayed.
65 | ///
66 | public enum SystemMenuTheme
67 | {
68 | ///
69 | /// Auto system theme.
70 | ///
71 | Auto = ApplicationTheme.Unknown,
72 |
73 | ///
74 | /// Dark system theme.
75 | ///
76 | Dark = ApplicationTheme.Dark,
77 |
78 | ///
79 | /// Light system theme.
80 | ///
81 | Light = ApplicationTheme.Light,
82 | }
83 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Windows;
3 | using System.Windows.Markup;
4 |
5 | [assembly: ComVisible(false)]
6 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
7 | [assembly: Guid("617d6360-3976-4871-85cb-36bdb4f7ab62")]
8 | [assembly: XmlnsPrefix("http://schemas.lepo.co/wpfui/2022/xaml", "ui")]
9 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml", "Wpf.Ui.Controls")]
10 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml", "Wpf.Ui.Controls.Primitives")]
11 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml", "Wpf.Ui")]
12 | [assembly: XmlnsPrefix("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "vio")]
13 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "Wpf.Ui.Violeta.Controls")]
14 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "Wpf.Ui.Violeta.Converters")]
15 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "Wpf.Ui.Violeta.Controls.Primitives")]
16 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "Wpf.Ui.Violeta.Markup")]
17 | [assembly: XmlnsDefinition("http://schemas.lepo.co/wpfui/2022/xaml/violeta", "Wpf.Ui.Violeta")]
18 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/BitmapIcon/BitmapIcon.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Media;
4 | using System.Windows.Media.Imaging;
5 | using System.Windows.Shapes;
6 | using Wpf.Ui.Violeta.Controls.Primitives;
7 |
8 | namespace Wpf.Ui.Controls;
9 |
10 | ///
11 | /// Represents an icon that uses a bitmap as its content.
12 | ///
13 | public class BitmapIcon : IconElementEx
14 | {
15 | static BitmapIcon()
16 | {
17 | ForegroundProperty.OverrideMetadata(typeof(BitmapIcon), new FrameworkPropertyMetadata(OnForegroundChanged));
18 | }
19 |
20 | ///
21 | /// Initializes a new instance of the BitmapIcon class.
22 | ///
23 | public BitmapIcon()
24 | {
25 | }
26 |
27 | ///
28 | /// Identifies the UriSource dependency property.
29 | ///
30 | public static readonly DependencyProperty UriSourceProperty =
31 | BitmapImage.UriSourceProperty.AddOwner(
32 | typeof(BitmapIcon),
33 | new FrameworkPropertyMetadata(OnUriSourceChanged));
34 |
35 | ///
36 | /// Gets or sets the Uniform Resource Identifier (URI) of the bitmap to use as the
37 | /// icon content.
38 | ///
39 | /// The Uri of the bitmap to use as the icon content. The default is **null**.
40 | public Uri UriSource
41 | {
42 | get => (Uri)GetValue(UriSourceProperty);
43 | set => SetValue(UriSourceProperty, value);
44 | }
45 |
46 | private static void OnUriSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
47 | {
48 | ((BitmapIcon)d).ApplyUriSource();
49 | }
50 |
51 | ///
52 | /// Identifies the ShowAsMonochrome dependency property.
53 | ///
54 | public static readonly DependencyProperty ShowAsMonochromeProperty =
55 | DependencyProperty.Register(
56 | nameof(ShowAsMonochrome),
57 | typeof(bool),
58 | typeof(BitmapIcon),
59 | new PropertyMetadata(true, OnShowAsMonochromeChanged));
60 |
61 | ///
62 | /// Gets or sets a value that indicates whether the bitmap is shown in a single color.
63 | ///
64 | ///
65 | /// **true** to show the bitmap in a single color; **false** to show the bitmap in
66 | /// full color. The default is **true.**
67 | ///
68 | public bool ShowAsMonochrome
69 | {
70 | get => (bool)GetValue(ShowAsMonochromeProperty);
71 | set => SetValue(ShowAsMonochromeProperty, value);
72 | }
73 |
74 | private static void OnShowAsMonochromeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
75 | {
76 | ((BitmapIcon)d).ApplyShowAsMonochrome();
77 | }
78 |
79 | protected override UIElement InitializeChildren()
80 | {
81 | _image = new Image
82 | {
83 | Visibility = Visibility.Hidden
84 | };
85 |
86 | _opacityMask = new ImageBrush();
87 | _foreground = new Rectangle
88 | {
89 | OpacityMask = _opacityMask
90 | };
91 |
92 | ApplyForeground();
93 | ApplyUriSource();
94 |
95 | Children.Add(_image);
96 |
97 | ApplyShowAsMonochrome();
98 |
99 | return _image;
100 | }
101 |
102 | private protected override void OnShouldInheritForegroundFromVisualParentChanged()
103 | {
104 | ApplyForeground();
105 | }
106 |
107 | private protected override void OnVisualParentForegroundPropertyChanged(DependencyPropertyChangedEventArgs args)
108 | {
109 | if (ShouldInheritForegroundFromVisualParent)
110 | {
111 | ApplyForeground();
112 | }
113 | }
114 |
115 | private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
116 | {
117 | ((BitmapIcon)d).ApplyForeground();
118 | }
119 |
120 | private void ApplyForeground()
121 | {
122 | if (_foreground != null)
123 | {
124 | _foreground.Fill = ShouldInheritForegroundFromVisualParent ? VisualParentForeground : Foreground;
125 | }
126 | }
127 |
128 | private void ApplyUriSource()
129 | {
130 | if (_image != null && _opacityMask != null)
131 | {
132 | var uriSource = UriSource;
133 | if (uriSource != null)
134 | {
135 | var imageSource = new BitmapImage(uriSource);
136 | _image.Source = imageSource;
137 | _opacityMask.ImageSource = imageSource;
138 | }
139 | else
140 | {
141 | _image.ClearValue(Image.SourceProperty);
142 | _opacityMask.ClearValue(ImageBrush.ImageSourceProperty);
143 | }
144 | }
145 | }
146 |
147 | private void ApplyShowAsMonochrome()
148 | {
149 | bool showAsMonochrome = ShowAsMonochrome;
150 |
151 | if (_image != null)
152 | {
153 | _image.Visibility = showAsMonochrome ? Visibility.Hidden : Visibility.Visible;
154 | }
155 |
156 | if (_foreground != null)
157 | {
158 | if (showAsMonochrome)
159 | {
160 | if (!Children.Contains(_foreground))
161 | {
162 | Children.Add(_foreground);
163 | }
164 | }
165 | else
166 | {
167 | Children.Remove(_foreground);
168 | }
169 | }
170 | }
171 |
172 | private Image _image = null!;
173 | private Rectangle _foreground = null!;
174 | private ImageBrush _opacityMask = null!;
175 | }
176 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/Button/Button.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
48 |
49 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/CachedImage/CachedImage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Cache;
3 | using System.Windows;
4 | using System.Windows.Media.Imaging;
5 |
6 | namespace Wpf.Ui.Violeta.Controls;
7 |
8 | ///
9 | /// Represents a control that is a wrapper on System.Windows.Controls.Image for enabling filesystem-based caching
10 | ///
11 | public class CachedImage : System.Windows.Controls.Image
12 | {
13 | static CachedImage()
14 | {
15 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CachedImage), new FrameworkPropertyMetadata(typeof(CachedImage)));
16 | }
17 |
18 | public string ImageUrl
19 | {
20 | get => (string)GetValue(ImageUrlProperty);
21 | set => SetValue(ImageUrlProperty, value);
22 | }
23 |
24 | public static readonly DependencyProperty ImageUrlProperty =
25 | DependencyProperty.Register(nameof(ImageUrl), typeof(string), typeof(CachedImage), new(string.Empty, ImageUrlPropertyChanged));
26 |
27 | public BitmapCreateOptions CreateOptions
28 | {
29 | get => (BitmapCreateOptions)GetValue(CreateOptionsProperty);
30 | set => SetValue(CreateOptionsProperty, value);
31 | }
32 |
33 | public static readonly DependencyProperty CreateOptionsProperty =
34 | DependencyProperty.Register(nameof(CreateOptions), typeof(BitmapCreateOptions), typeof(CachedImage));
35 |
36 | private static async void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
37 | {
38 | var url = e.NewValue as string;
39 |
40 | if (string.IsNullOrEmpty(url))
41 | return;
42 |
43 | var cachedImage = (CachedImage)obj;
44 | var bitmapImage = new BitmapImage();
45 |
46 | switch (FileCacheForImage.AppCacheMode)
47 | {
48 | case FileCacheForImage.CacheMode.WinINet:
49 | bitmapImage.BeginInit();
50 | bitmapImage.CreateOptions = cachedImage.CreateOptions;
51 | bitmapImage.UriSource = new Uri(url);
52 | // Enable IE-like cache policy.
53 | bitmapImage.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
54 | bitmapImage.EndInit();
55 | cachedImage.Source = bitmapImage;
56 | break;
57 |
58 | case FileCacheForImage.CacheMode.Dedicated:
59 | try
60 | {
61 | var memoryStream = await FileCacheForImage.HitAsync(url!);
62 | if (memoryStream == null)
63 | return;
64 |
65 | bitmapImage.BeginInit();
66 | bitmapImage.CreateOptions = cachedImage.CreateOptions;
67 | bitmapImage.StreamSource = memoryStream;
68 | bitmapImage.EndInit();
69 | cachedImage.Source = bitmapImage;
70 | }
71 | catch (Exception)
72 | {
73 | // ignored, in case the downloaded file is a broken or not an image.
74 | }
75 | break;
76 |
77 | default:
78 | throw new ArgumentOutOfRangeException();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/CachedImage/FileCacheForImage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Security.Cryptography;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Wpf.Ui.Violeta.Controls;
12 |
13 | public static class FileCacheForImage
14 | {
15 | public enum CacheMode
16 | {
17 | WinINet,
18 | Dedicated
19 | }
20 |
21 | // Record whether a file is being written.
22 | private static readonly Dictionary IsWritingFile = [];
23 |
24 | // Timeout for performing the file download request.
25 | private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(5);
26 |
27 | // HttpClient is intended to be instantiated once per application, rather than per-use.
28 | private static readonly Lazy LazyHttpClient = new(() => new HttpClient());
29 |
30 | static FileCacheForImage()
31 | {
32 | // default cache directory - can be changed if needed from App.xaml
33 | AppCacheDirectory = string.Format("{0}\\{1}\\Cache\\",
34 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
35 | Process.GetCurrentProcess().ProcessName);
36 | AppCacheMode = CacheMode.WinINet;
37 | }
38 |
39 | ///
40 | /// Gets or sets the path to the folder that stores the cache file. Only works when AppCacheMode is
41 | /// CacheMode.Dedicated.
42 | ///
43 | public static string AppCacheDirectory { get; set; }
44 |
45 | ///
46 | /// Gets or sets the cache mode. WinINet is recommended, it's provided by .Net Framework and uses the Temporary Files
47 | /// of IE and the same cache policy of IE.
48 | ///
49 | public static CacheMode AppCacheMode { get; set; }
50 |
51 | public static async Task HitAsync(string url)
52 | {
53 | if (!Directory.Exists(AppCacheDirectory))
54 | {
55 | Directory.CreateDirectory(AppCacheDirectory);
56 | }
57 | var uri = new Uri(url);
58 | var fileNameBuilder = new StringBuilder();
59 | using (SHA1 sha1 = SHA1.Create())
60 | {
61 | var canonicalUrl = uri.ToString();
62 | var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(canonicalUrl));
63 | fileNameBuilder.Append(BitConverter.ToString(hash).Replace("-", "").ToLower());
64 | if (Path.HasExtension(canonicalUrl))
65 | {
66 | fileNameBuilder.Append(Path.GetExtension(canonicalUrl).Split('?')[0]);
67 | }
68 | }
69 |
70 | var fileName = fileNameBuilder.ToString();
71 | var localFile = string.Format("{0}\\{1}", AppCacheDirectory, fileName);
72 | var memoryStream = new MemoryStream();
73 |
74 | FileStream fileStream = null!;
75 | if (!IsWritingFile.ContainsKey(fileName) && File.Exists(localFile))
76 | {
77 | using (fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
78 | {
79 | await fileStream.CopyToAsync(memoryStream);
80 | }
81 | memoryStream.Seek(0, SeekOrigin.Begin);
82 | return memoryStream;
83 | }
84 |
85 | var client = LazyHttpClient.Value;
86 | client.Timeout = RequestTimeout;
87 | try
88 | {
89 | var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
90 |
91 | if (response.IsSuccessStatusCode is false)
92 | {
93 | return null!;
94 | }
95 |
96 | var responseStream = await response.Content.ReadAsStreamAsync();
97 |
98 | if (!IsWritingFile.ContainsKey(fileName))
99 | {
100 | IsWritingFile[fileName] = true;
101 | fileStream = new FileStream(localFile, FileMode.Create, FileAccess.Write);
102 | }
103 |
104 | using (responseStream)
105 | {
106 | var bytebuffer = new byte[100];
107 | int bytesRead;
108 | do
109 | {
110 | bytesRead = await responseStream.ReadAsync(bytebuffer, 0, 100);
111 | if (fileStream != null)
112 | {
113 | await fileStream.WriteAsync(bytebuffer, 0, bytesRead);
114 | }
115 |
116 | await memoryStream.WriteAsync(bytebuffer, 0, bytesRead);
117 | } while (bytesRead > 0);
118 | if (fileStream != null)
119 | {
120 | await fileStream.FlushAsync();
121 | fileStream.Dispose();
122 | IsWritingFile.Remove(fileName);
123 | }
124 | }
125 | memoryStream.Seek(0, SeekOrigin.Begin);
126 | return memoryStream;
127 | }
128 | catch (WebException)
129 | {
130 | return null!;
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogButton.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Violeta.Controls;
2 |
3 | public enum ContentDialogButton
4 | {
5 | None = 0,
6 | Primary = 1,
7 | Secondary = 2,
8 | Close = 3,
9 | }
10 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogButtonClickDeferral.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public sealed class ContentDialogButtonClickDeferral
6 | {
7 | private readonly Action _handler;
8 |
9 | internal ContentDialogButtonClickDeferral(Action handler)
10 | {
11 | _handler = handler ?? throw new ArgumentNullException(nameof(handler));
12 | }
13 |
14 | public void Complete()
15 | {
16 | _handler();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogButtonClickEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public class ContentDialogButtonClickEventArgs : RoutedEventArgs
7 | {
8 | private ContentDialogButtonClickDeferral _deferral = null!;
9 | private int _deferralCount;
10 |
11 | internal ContentDialogButtonClickEventArgs()
12 | {
13 | }
14 |
15 | public bool Cancel { get; set; }
16 |
17 | public ContentDialogButtonClickDeferral GetDeferral()
18 | {
19 | _deferralCount++;
20 |
21 | return new ContentDialogButtonClickDeferral(() =>
22 | {
23 | DecrementDeferralCount();
24 | });
25 | }
26 |
27 | internal void SetDeferral(ContentDialogButtonClickDeferral deferral)
28 | {
29 | _deferral = deferral;
30 | }
31 |
32 | internal void DecrementDeferralCount()
33 | {
34 | Debug.Assert(_deferralCount > 0);
35 | _deferralCount--;
36 | if (_deferralCount == 0)
37 | {
38 | _deferral.Complete();
39 | }
40 | }
41 |
42 | internal void IncrementDeferralCount()
43 | {
44 | _deferralCount++;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogClosedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public class ContentDialogClosedEventArgs : RoutedEventArgs
6 | {
7 | internal ContentDialogClosedEventArgs(ContentDialogResult result)
8 | {
9 | Result = result;
10 | }
11 |
12 | public ContentDialogResult Result { get; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogClosingDeferral.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public sealed class ContentDialogClosingDeferral
6 | {
7 | private readonly Action _handler;
8 |
9 | internal ContentDialogClosingDeferral(Action handler)
10 | {
11 | _handler = handler ?? throw new ArgumentNullException(nameof(handler));
12 | }
13 |
14 | public void Complete()
15 | {
16 | _handler();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogClosingEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public sealed class ContentDialogClosingEventArgs : RoutedEventArgs
7 | {
8 | private ContentDialogClosingDeferral _deferral = null!;
9 | private int _deferralCount;
10 |
11 | internal ContentDialogClosingEventArgs(ContentDialogResult result)
12 | {
13 | Result = result;
14 | }
15 |
16 | public bool Cancel { get; set; }
17 |
18 | public ContentDialogResult Result { get; }
19 |
20 | public ContentDialogClosingDeferral GetDeferral()
21 | {
22 | _deferralCount++;
23 |
24 | return new ContentDialogClosingDeferral(() =>
25 | {
26 | DecrementDeferralCount();
27 | });
28 | }
29 |
30 | internal void SetDeferral(ContentDialogClosingDeferral deferral)
31 | {
32 | _deferral = deferral;
33 | }
34 |
35 | internal void DecrementDeferralCount()
36 | {
37 | Debug.Assert(_deferralCount > 0);
38 | _deferralCount--;
39 | if (_deferralCount == 0)
40 | {
41 | _deferral.Complete();
42 | }
43 | }
44 |
45 | internal void IncrementDeferralCount()
46 | {
47 | _deferralCount++;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogHostService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | namespace Wpf.Ui.Controls;
7 |
8 | public static class ContentDialogHostService
9 | {
10 | public const string HostName = "PART_ContentPresenterForDialogs";
11 |
12 | public static bool IsPreferMainWindow { get; set; } = true;
13 |
14 | public static ContentPresenter? ContentPresenterForDialogs
15 | {
16 | get
17 | {
18 | if (IsPreferMainWindow && Application.Current.MainWindow != null)
19 | {
20 | return Application.Current.MainWindow.Dispatcher.Invoke(() => AttachContentDialogHost(Application.Current.MainWindow));
21 | }
22 | else
23 | {
24 | Window? window = Application.Current.Windows.OfType()
25 | .Where(win => win.IsActive)
26 | .FirstOrDefault();
27 |
28 | if (window != null)
29 | {
30 | return window.Dispatcher.Invoke(() => AttachContentDialogHost(window));
31 | }
32 | }
33 |
34 | // Throw InvalidOperationException if ContentDialog::ShowAsync is called with a null DialogHost.
35 | return null;
36 |
37 | static ContentPresenter? AttachContentDialogHost(Window window)
38 | {
39 | if (window.Content is System.Windows.Controls.Grid rootGrid)
40 | {
41 | if (rootGrid.ColumnDefinitions.Count != 0)
42 | {
43 | throw new InvalidOperationException("The root Grid's ColumnDefinitions in the Window should be empty.");
44 | }
45 | if (rootGrid.RowDefinitions.Count != 0)
46 | {
47 | throw new InvalidOperationException("The root Grid's RowDefinitions in the Window should be empty.");
48 | }
49 |
50 | if (window.FindName(HostName) is ContentPresenter _host)
51 | {
52 | return _host;
53 | }
54 | else
55 | {
56 | ContentPresenter host = new() { Name = HostName };
57 | rootGrid.Children.Add(host);
58 | window.RegisterName(HostName, host);
59 | return host;
60 | }
61 | }
62 | else
63 | {
64 | throw new InvalidOperationException("The root element of the Window must be of type Grid.");
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogOpenedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public class ContentDialogOpenedEventArgs : RoutedEventArgs;
6 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogPlacement.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Violeta.Controls;
2 |
3 | public enum ContentDialogPlacement
4 | {
5 | Popup = 0,
6 | InPlace = 1,
7 | }
8 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/ContentDialogResult.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Violeta.Controls;
2 |
3 | public enum ContentDialogResult
4 | {
5 | None = 0,
6 | Primary = 1,
7 | Secondary = 2,
8 | }
9 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ContentDialog/CornerRadiusFilterConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace Wpf.Ui.Violeta.Controls;
7 |
8 | public sealed class CornerRadiusFilterConverter : DependencyObject, IValueConverter
9 | {
10 | public CornerRadiusFilterKind Filter { get; set; }
11 |
12 | public double Scale { get; set; } = 1d;
13 |
14 | public static CornerRadius Convert(CornerRadius radius, CornerRadiusFilterKind filterKind)
15 | {
16 | CornerRadius result = radius;
17 | switch (filterKind)
18 | {
19 | case CornerRadiusFilterKind.Top:
20 | result.BottomLeft = 0.0;
21 | result.BottomRight = 0.0;
22 | break;
23 |
24 | case CornerRadiusFilterKind.Right:
25 | result.TopLeft = 0.0;
26 | result.BottomLeft = 0.0;
27 | break;
28 |
29 | case CornerRadiusFilterKind.Bottom:
30 | result.TopLeft = 0.0;
31 | result.TopRight = 0.0;
32 | break;
33 |
34 | case CornerRadiusFilterKind.Left:
35 | result.TopRight = 0.0;
36 | result.BottomRight = 0.0;
37 | break;
38 | }
39 |
40 | return result;
41 | }
42 |
43 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
44 | {
45 | if (value is CornerRadius radius)
46 | {
47 | double scale = Scale;
48 | if (!double.IsNaN(scale))
49 | {
50 | radius.TopLeft *= scale;
51 | radius.TopRight *= scale;
52 | radius.BottomRight *= scale;
53 | radius.BottomLeft *= scale;
54 | }
55 |
56 | CornerRadiusFilterKind filter = Filter;
57 | if (filter == CornerRadiusFilterKind.TopLeftValue || filter == CornerRadiusFilterKind.BottomRightValue)
58 | {
59 | return GetDoubleValue(radius, filter);
60 | }
61 |
62 | return Convert(radius, filter);
63 | }
64 |
65 | return new CornerRadius(0.0);
66 | }
67 |
68 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
69 | {
70 | return new CornerRadius(0.0);
71 | }
72 |
73 | private double GetDoubleValue(CornerRadius radius, CornerRadiusFilterKind filterKind)
74 | {
75 | return filterKind switch
76 | {
77 | CornerRadiusFilterKind.TopLeftValue => radius.TopLeft,
78 | CornerRadiusFilterKind.BottomRightValue => radius.BottomRight,
79 | _ => 0.0,
80 | };
81 | }
82 | }
83 |
84 | public enum CornerRadiusFilterKind
85 | {
86 | None,
87 | Top,
88 | Right,
89 | Bottom,
90 | Left,
91 | TopLeftValue,
92 | BottomRightValue
93 | }
94 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/DataGrid/DataGrid.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
30 |
31 |
46 |
47 |
48 |
54 |
55 |
56 |
64 |
65 |
66 |
90 |
91 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ExceptionReport/ExceptionReport.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows;
6 | using System.Windows.Threading;
7 |
8 | namespace Wpf.Ui.Violeta.Controls;
9 |
10 | public static class ExceptionReport
11 | {
12 | ///
13 | /// For safety only use `Application.Current.Dispatcher` for show dialog.
14 | /// It means the temporary STA thread will be skipped.
15 | ///
16 | public static bool IsOnlyApplicationDispatcher { get; set; } = true;
17 |
18 | public static void HandleOnUnhandledException(this Application app)
19 | {
20 | app.DispatcherUnhandledException -= OnApplicationUnhandledException;
21 | app.DispatcherUnhandledException += OnApplicationUnhandledException;
22 | }
23 |
24 | private static void OnApplicationUnhandledException(object? sender, DispatcherUnhandledExceptionEventArgs e)
25 | {
26 | e.Handled = true;
27 |
28 | Debug.WriteLine("[ExceptionReport] Application.DispatcherUnhandledException " + e.Exception.ToString());
29 | Show(e.Exception);
30 | }
31 |
32 | public static void Show(Exception e, string? appName = null, string? appVersion = null)
33 | {
34 | Window? owner = Application.Current.Windows.OfType().FirstOrDefault(window => window.IsActive)
35 | ?? Application.Current.MainWindow;
36 |
37 | Dispatcher dispatcher = Application.Current.Dispatcher;
38 |
39 | if (!IsOnlyApplicationDispatcher)
40 | {
41 | dispatcher = owner?.Dispatcher ?? Application.Current.Dispatcher;
42 | }
43 |
44 | _ = dispatcher.Invoke(() => _ = new ExceptionWindow(e, appName, appVersion) { Owner = owner }.ShowDialog());
45 | }
46 |
47 | public static async Task ShowAsync(Exception e, string? appName = null, string? appVersion = null)
48 | {
49 | Window? owner = Application.Current.Windows.OfType().FirstOrDefault(window => window.IsActive)
50 | ?? Application.Current.MainWindow;
51 |
52 | Dispatcher dispatcher = Application.Current.Dispatcher;
53 |
54 | if (!IsOnlyApplicationDispatcher)
55 | {
56 | dispatcher = owner?.Dispatcher ?? Application.Current.Dispatcher;
57 | }
58 |
59 | await dispatcher.BeginInvoke(() => _ = new ExceptionWindow(e, appName, appVersion) { Owner = owner }.ShowDialog());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ExceptionReport/ExceptionWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 | using Wpf.Ui.Controls;
7 | using Wpf.Ui.Violeta.Resources.Localization;
8 | using Wpf.Ui.Violeta.Win32;
9 |
10 | namespace Wpf.Ui.Violeta.Controls;
11 |
12 | public partial class ExceptionWindow : FluentWindow
13 | {
14 | public Window? owner = null;
15 |
16 | public new Window? Owner
17 | {
18 | get => owner;
19 | set
20 | {
21 | owner = value;
22 |
23 | if (owner != null)
24 | {
25 | try
26 | {
27 | // Try get title but be care the owner had been disposed.
28 | AppName ??= owner.Dispatcher.Invoke(() => owner.Title);
29 | }
30 | catch
31 | {
32 | ///
33 | }
34 |
35 | try
36 | {
37 | // Try get assembly version but be care the owner class had been unmounted.
38 | AppVersion ??= "v" + owner.GetType().Assembly.GetName().Version?.ToString();
39 | }
40 | catch
41 | {
42 | ///
43 | }
44 | }
45 | }
46 | }
47 |
48 | public Exception? ExceptionObject { get; set; }
49 | public string? ExceptionType => ExceptionObject?.GetType().ToString();
50 |
51 | public string? AppName
52 | {
53 | get => (string)GetValue(AppNameProperty);
54 | set => SetValue(AppNameProperty, value);
55 | }
56 |
57 | public static readonly DependencyProperty AppNameProperty =
58 | DependencyProperty.Register(nameof(AppName), typeof(string), typeof(ExceptionWindow), new PropertyMetadata(null));
59 |
60 | public string? AppVersion
61 | {
62 | get => (string)GetValue(AppVersionProperty);
63 | set => SetValue(AppVersionProperty, value);
64 | }
65 |
66 | public static readonly DependencyProperty AppVersionProperty =
67 | DependencyProperty.Register(nameof(AppVersion), typeof(string), typeof(ExceptionWindow), new PropertyMetadata(null));
68 |
69 | public string ErrorTime { get; } = DateTime.Now.ToString();
70 |
71 | public string OSVersion { get; } = "Windows " + NTdll.GetOSVersion().ToString();
72 |
73 | public ExceptionWindow(Exception e, string? appName = null, string? appVersion = null)
74 | {
75 | ExceptionObject = e ?? new Exception("");
76 | AppName = appName;
77 | AppVersion = appVersion;
78 |
79 | DataContext = this;
80 | InitializeComponent();
81 |
82 | Title = SH.ExceptionWindowTitle;
83 | Hint1TextBlock.Text = SH.ExceptionWindowHint1;
84 | Hint2TextBlock.Text = SH.ExceptionWindowHint2;
85 | CopyTextBlock.Text = SH.ButtonCopy;
86 | IgnoreTextBlock.Text = SH.ButtonIgnore;
87 | ExitTextBlock.Text = SH.ButtonExit;
88 |
89 | CopyButton.Click += (_, _) => CopyInfo();
90 | TryIgnoreButton.Click += (_, _) => IgnoreAndTry();
91 | ExitButton.Click += (_, _) => ExitApp();
92 |
93 | KeyDown += (_, e) =>
94 | {
95 | if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && e.Key == Key.C)
96 | {
97 | CopyInfo();
98 | }
99 | };
100 | }
101 |
102 | protected override void OnSourceInitialized(EventArgs e)
103 | {
104 | base.OnSourceInitialized(e);
105 |
106 | if (WindowBackdrop.IsSupported(WindowBackdropType.Mica))
107 | {
108 | Background = new SolidColorBrush(Colors.Transparent);
109 | WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica);
110 | }
111 | }
112 |
113 | public string GetInfo()
114 | {
115 | StringBuilder sb = new();
116 |
117 | sb.AppendLine($"{AppName} {AppVersion}")
118 | .AppendLine($"{ErrorTime} {OSVersion}")
119 | .AppendLine("```")
120 | .AppendLine(ExceptionObject?.ToString())
121 | .AppendLine("```");
122 |
123 | return sb.ToString();
124 | }
125 |
126 | public void CopyInfo()
127 | {
128 | try
129 | {
130 | Clipboard.SetText(GetInfo());
131 | }
132 | catch
133 | {
134 | ///
135 | }
136 | }
137 |
138 | public void IgnoreAndTry()
139 | {
140 | Close();
141 | }
142 |
143 | public void ExitApp()
144 | {
145 | IgnoreAndTry();
146 | Environment.Exit('e' + 'x' + 'c' + 'e' + 'p' + 't' + 'i' + 'o' + 'n');
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/FontIcon/CustomFontSymbols.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Controls;
2 |
3 | ///
4 | /// Violeta Fluent Icons
5 | /// https://github.com/emako/fluentui-violeta-icons
6 | ///
7 | public sealed class CustomFontSymbols
8 | {
9 | public const string Edit = "\xe900";
10 | public const string Empty = "\xe901";
11 | public const string Extension = "\xe902";
12 | public const string File = "\xe903";
13 | }
14 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/FontIcon/FontIconFallback.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Media;
5 |
6 | namespace Wpf.Ui.Violeta.Controls;
7 |
8 | // TODO: Use font icon if available
9 | [EditorBrowsable(EditorBrowsableState.Never)]
10 | public sealed class FontIconFallback : Control
11 | {
12 | static FontIconFallback()
13 | {
14 | DefaultStyleKeyProperty.OverrideMetadata(typeof(FontIconFallback), new FrameworkPropertyMetadata(typeof(FontIconFallback)));
15 | FocusableProperty.OverrideMetadata(typeof(FontIconFallback), new FrameworkPropertyMetadata(false));
16 | }
17 |
18 | public static readonly DependencyProperty DataProperty =
19 | DependencyProperty.Register(nameof(Data), typeof(Geometry), typeof(FontIconFallback), null);
20 |
21 | public Geometry Data
22 | {
23 | get => (Geometry)GetValue(DataProperty);
24 | set => SetValue(DataProperty, value);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/FontIcon/FontIconFallback.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ImageView/AnimatedImage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 |
8 | namespace Wpf.Ui.Violeta.Controls;
9 |
10 | public class AnimatedImage : Image, IDisposable
11 | {
12 | public static List> Providers { get; } = [];
13 |
14 | private AnimationProvider _animation = null!;
15 | private bool _disposing;
16 |
17 | static AnimatedImage()
18 | {
19 | Providers.Add(
20 | new KeyValuePair(
21 | [".apng", ".png", ".jpg", ".jpeg", ".bmp"],
22 | typeof(BitmapImageProvider)));
23 | }
24 |
25 | public void Dispose()
26 | {
27 | _disposing = true;
28 |
29 | BeginAnimation(AnimationFrameIndexProperty, null);
30 | Source = null;
31 |
32 | _animation?.Dispose();
33 | _animation = null!;
34 | }
35 |
36 | public event EventHandler? ImageLoaded;
37 |
38 | public event EventHandler? DoZoomToFit;
39 |
40 | private static AnimationProvider InitAnimationProvider(Uri path)
41 | {
42 | var ext = Path.GetExtension(path.LocalPath).ToLower();
43 | var type = Providers.First(p => p.Key.Contains(ext) || p.Key.Contains("*")).Value;
44 |
45 | var provider = type.CreateInstance(path);
46 |
47 | return provider;
48 | }
49 |
50 | public static readonly DependencyProperty AnimationFrameIndexProperty =
51 | DependencyProperty.Register(nameof(AnimationFrameIndex), typeof(int), typeof(AnimatedImage), new UIPropertyMetadata(-1, AnimationFrameIndexChanged));
52 |
53 | public static readonly DependencyProperty AnimationUriProperty =
54 | DependencyProperty.Register(nameof(AnimationUri), typeof(Uri), typeof(AnimatedImage), new UIPropertyMetadata(null, AnimationUriChanged));
55 |
56 | public int AnimationFrameIndex
57 | {
58 | get => (int)GetValue(AnimationFrameIndexProperty);
59 | set => SetValue(AnimationFrameIndexProperty, value);
60 | }
61 |
62 | public Uri AnimationUri
63 | {
64 | get => (Uri)GetValue(AnimationUriProperty);
65 | set => SetValue(AnimationUriProperty, value);
66 | }
67 |
68 | private static void AnimationUriChanged(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
69 | {
70 | if (obj is not AnimatedImage instance)
71 | {
72 | return;
73 | }
74 |
75 | instance._animation = InitAnimationProvider((Uri)ev.NewValue);
76 | ShowThumbnailAndStartAnimation(instance);
77 | }
78 |
79 | private static void ShowThumbnailAndStartAnimation(AnimatedImage instance)
80 | {
81 | instance.Dispatcher.Invoke(() =>
82 | {
83 | if (instance._disposing)
84 | {
85 | return;
86 | }
87 |
88 | instance.DoZoomToFit?.Invoke(instance, new EventArgs());
89 | instance.ImageLoaded?.Invoke(instance, new EventArgs());
90 |
91 | instance.BeginAnimation(AnimationFrameIndexProperty, instance._animation?.Animator);
92 | });
93 | }
94 |
95 | private static void AnimationFrameIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
96 | {
97 | if (obj is not AnimatedImage instance)
98 | {
99 | return;
100 | }
101 |
102 | if (instance._disposing)
103 | {
104 | return;
105 | }
106 |
107 | var task = instance._animation.GetRenderedFrame((int)ev.NewValue);
108 |
109 | task.ContinueWith(_ => instance.Dispatcher.Invoke(() =>
110 | {
111 | if (instance._disposing)
112 | return;
113 |
114 | var firstLoad = instance.Source == null;
115 |
116 | instance.Source = _.Result;
117 |
118 | if (firstLoad)
119 | {
120 | instance.DoZoomToFit?.Invoke(instance, new EventArgs());
121 | instance.ImageLoaded?.Invoke(instance, new EventArgs());
122 | }
123 | }));
124 | }
125 | }
126 |
127 | file static class TypeExtensions
128 | {
129 | public static T CreateInstance(this Type t, params object[] paramArray)
130 | {
131 | return (T)Activator.CreateInstance(t, paramArray)!;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ImageView/AnimationProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Windows.Media.Animation;
4 | using System.Windows.Media.Imaging;
5 |
6 | namespace Wpf.Ui.Violeta.Controls;
7 |
8 | internal abstract class AnimationProvider : IDisposable
9 | {
10 | protected AnimationProvider(Uri path)
11 | {
12 | Path = path;
13 | Animator = new();
14 | Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.Zero)));
15 | }
16 |
17 | public Uri Path { get; }
18 |
19 | public Int32AnimationUsingKeyFrames Animator { get; protected set; }
20 |
21 | public abstract void Dispose();
22 |
23 | public abstract Task GetRenderedFrame(int index);
24 | }
25 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ImageView/BitmapImageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Windows.Media.Imaging;
4 |
5 | namespace Wpf.Ui.Violeta.Controls;
6 |
7 | internal class BitmapImageProvider(Uri path) : AnimationProvider(path)
8 | {
9 | public override void Dispose()
10 | {
11 | ///
12 | }
13 |
14 | public override Task GetRenderedFrame(int index)
15 | {
16 | return Task.FromResult(new BitmapImage(Path));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/ImageView/ImageView.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
32 |
37 |
38 |
47 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/Layout/StackPanel.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Markup;
4 |
5 | namespace Wpf.Ui.Controls;
6 |
7 | [ContentProperty(nameof(Children))]
8 | public class StackPanel : Panel
9 | {
10 | public static readonly DependencyProperty OrientationProperty =
11 | DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(StackPanel), new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsMeasure));
12 |
13 | public Orientation Orientation
14 | {
15 | get => (Orientation)GetValue(OrientationProperty);
16 | set => SetValue(OrientationProperty, value);
17 | }
18 |
19 | public static readonly DependencyProperty SpacingProperty =
20 | DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(StackPanel), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
21 |
22 | public double Spacing
23 | {
24 | get => (double)GetValue(SpacingProperty);
25 | set => SetValue(SpacingProperty, value);
26 | }
27 |
28 | protected override Size MeasureOverride(Size availableSize)
29 | {
30 | var spacing = Spacing;
31 | var panelDesiredSize = new Size();
32 |
33 | if (Orientation == Orientation.Vertical)
34 | {
35 | availableSize.Height = double.PositiveInfinity;
36 | foreach (UIElement child in InternalChildren)
37 | {
38 | child.Measure(availableSize);
39 | var childDesiredSize = child.DesiredSize;
40 |
41 | panelDesiredSize.Height += childDesiredSize.Height;
42 |
43 | if (child.Visibility != Visibility.Collapsed)
44 | {
45 | panelDesiredSize.Height += spacing;
46 | }
47 |
48 | if (childDesiredSize.Width > panelDesiredSize.Width)
49 | {
50 | panelDesiredSize.Width = childDesiredSize.Width;
51 | }
52 | }
53 |
54 | if (InternalChildren.Count != 0)
55 | {
56 | panelDesiredSize.Height -= spacing;
57 | }
58 | }
59 | else
60 | {
61 | availableSize.Width = double.PositiveInfinity;
62 | foreach (UIElement child in InternalChildren)
63 | {
64 | child.Measure(availableSize);
65 | var childDesiredSize = child.DesiredSize;
66 |
67 | panelDesiredSize.Width += childDesiredSize.Width;
68 |
69 | if (child.Visibility != Visibility.Collapsed)
70 | {
71 | panelDesiredSize.Width += spacing;
72 | }
73 |
74 | if (childDesiredSize.Height > panelDesiredSize.Height)
75 | {
76 | panelDesiredSize.Height = childDesiredSize.Height;
77 | }
78 | }
79 |
80 | if (InternalChildren.Count != 0)
81 | {
82 | panelDesiredSize.Width -= spacing;
83 | }
84 | }
85 |
86 | return panelDesiredSize;
87 | }
88 |
89 | protected override Size ArrangeOverride(Size finalSize)
90 | {
91 | var offset = 0d;
92 | var spacing = Spacing;
93 |
94 | if (Orientation == Orientation.Vertical)
95 | {
96 | foreach (UIElement child in InternalChildren)
97 | {
98 | var childDesiredSize = child.DesiredSize;
99 |
100 | child.Arrange(new Rect(0, offset, finalSize.Width, childDesiredSize.Height));
101 |
102 | offset += childDesiredSize.Height;
103 |
104 | if (child.Visibility != Visibility.Collapsed)
105 | {
106 | offset += spacing;
107 | }
108 | }
109 | }
110 | else
111 | {
112 | foreach (UIElement child in InternalChildren)
113 | {
114 | var childDesiredSize = child.DesiredSize;
115 |
116 | child.Arrange(new Rect(offset, 0, childDesiredSize.Width, finalSize.Height));
117 |
118 | offset += childDesiredSize.Width;
119 | if (child.Visibility != Visibility.Collapsed)
120 | {
121 | offset += spacing;
122 | }
123 | }
124 | }
125 |
126 | return finalSize;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxButtonClickDeferral.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public sealed class MessageBoxButtonClickDeferral
6 | {
7 | private readonly Action _handler;
8 |
9 | internal MessageBoxButtonClickDeferral(Action handler)
10 | {
11 | _handler = handler ?? throw new ArgumentNullException(nameof(handler));
12 | }
13 |
14 | public void Complete()
15 | {
16 | _handler();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxButtonClickEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public class MessageBoxButtonClickEventArgs : RoutedEventArgs
7 | {
8 | private MessageBoxButtonClickDeferral _deferral = null!;
9 | private int _deferralCount;
10 |
11 | internal MessageBoxButtonClickEventArgs()
12 | {
13 | }
14 |
15 | public bool Cancel { get; set; }
16 |
17 | public MessageBoxButtonClickDeferral GetDeferral()
18 | {
19 | _deferralCount++;
20 |
21 | return new MessageBoxButtonClickDeferral(() =>
22 | {
23 | DecrementDeferralCount();
24 | });
25 | }
26 |
27 | internal void SetDeferral(MessageBoxButtonClickDeferral deferral)
28 | {
29 | _deferral = deferral;
30 | }
31 |
32 | internal void DecrementDeferralCount()
33 | {
34 | Debug.Assert(_deferralCount > 0);
35 | _deferralCount--;
36 | if (_deferralCount == 0)
37 | {
38 | _deferral.Complete();
39 | }
40 | }
41 |
42 | internal void IncrementDeferralCount()
43 | {
44 | _deferralCount++;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxClosedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public class MessageBoxClosedEventArgs : RoutedEventArgs
6 | {
7 | internal MessageBoxClosedEventArgs(MessageBoxResult result)
8 | {
9 | Result = result;
10 | }
11 |
12 | public MessageBoxResult Result { get; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxClosingDeferral.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public sealed class MessageBoxClosingDeferral
6 | {
7 | private readonly Action _handler;
8 |
9 | internal MessageBoxClosingDeferral(Action handler)
10 | {
11 | _handler = handler ?? throw new ArgumentNullException(nameof(handler));
12 | }
13 |
14 | public void Complete()
15 | {
16 | _handler();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxClosingEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public sealed class MessageBoxClosingEventArgs : RoutedEventArgs
7 | {
8 | private MessageBoxClosingDeferral _deferral = null!;
9 | private int _deferralCount;
10 |
11 | internal MessageBoxClosingEventArgs(MessageBoxResult result)
12 | {
13 | Result = result;
14 | }
15 |
16 | public bool Cancel { get; set; }
17 |
18 | public MessageBoxResult Result { get; }
19 |
20 | public MessageBoxClosingDeferral GetDeferral()
21 | {
22 | _deferralCount++;
23 |
24 | return new MessageBoxClosingDeferral(() =>
25 | {
26 | DecrementDeferralCount();
27 | });
28 | }
29 |
30 | internal void SetDeferral(MessageBoxClosingDeferral deferral)
31 | {
32 | _deferral = deferral;
33 | }
34 |
35 | internal void DecrementDeferralCount()
36 | {
37 | Debug.Assert(_deferralCount > 0);
38 | _deferralCount--;
39 | if (_deferralCount == 0)
40 | {
41 | _deferral.Complete();
42 | }
43 | }
44 |
45 | internal void IncrementDeferralCount()
46 | {
47 | _deferralCount++;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxContentTemplateSelector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 |
5 | namespace Wpf.Ui.Violeta.Controls;
6 |
7 | internal sealed class MessageBoxContentTemplateSelector : DataTemplateSelector
8 | {
9 | public DataTemplate? StringTemplate { get; set; }
10 | public DataTemplate? DefaultTemplate { get; set; }
11 |
12 | public override DataTemplate SelectTemplate(object item, DependencyObject container)
13 | {
14 | if (item is string)
15 | {
16 | return StringTemplate ?? throw new InvalidOperationException(nameof(StringTemplate));
17 | }
18 |
19 | return DefaultTemplate ?? throw new InvalidOperationException(nameof(DefaultTemplate));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxIcon.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Violeta.Controls;
2 |
3 | public enum MessageBoxIcon
4 | {
5 | None,
6 | Information,
7 | Success,
8 | Error,
9 | Warning,
10 | Question,
11 | }
12 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxIconConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 | using Wpf.Ui.Controls;
6 |
7 | namespace Wpf.Ui.Violeta.Controls;
8 |
9 | [ValueConversion(typeof(MessageBoxIcon), typeof(string))]
10 | public sealed class MessageBoxIconConverter : IValueConverter
11 | {
12 | public static MessageBoxIconConverter New => new();
13 |
14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | if (value is MessageBoxIcon icon)
17 | {
18 | return icon switch
19 | {
20 | MessageBoxIcon.Information => FontSymbols.Info,
21 | MessageBoxIcon.Success => FontSymbols.Accept,
22 | MessageBoxIcon.Warning => FontSymbols.Warning,
23 | MessageBoxIcon.Error => FontSymbols.Cancel,
24 | MessageBoxIcon.Question => FontSymbols.Unknown,
25 | MessageBoxIcon.None or _ => string.Empty,
26 | };
27 | }
28 | return string.Empty;
29 | }
30 |
31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
32 | {
33 | return DependencyProperty.UnsetValue;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxIconExtensions.cs:
--------------------------------------------------------------------------------
1 | using Wpf.Ui.Controls;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public static class MessageBoxIconExtensions
6 | {
7 | public static MessageBoxIcon ToMessageBoxIcon(this IconSource icon)
8 | {
9 | MessageBoxIcon boxIcon = MessageBoxIcon.None;
10 |
11 | if (icon is FontIconSource ficon)
12 | {
13 | if (ficon.Glyph == MessageBoxSymbolGlyph.Error.ToGlyph())
14 | {
15 | boxIcon = MessageBoxIcon.Error;
16 | }
17 | else if (ficon.Glyph == MessageBoxSymbolGlyph.Info.ToGlyph())
18 | {
19 | boxIcon = MessageBoxIcon.Information;
20 | }
21 | else if (ficon.Glyph == MessageBoxSymbolGlyph.Warning.ToGlyph())
22 | {
23 | boxIcon = MessageBoxIcon.Warning;
24 | }
25 | else if (ficon.Glyph == MessageBoxSymbolGlyph.Question.ToGlyph())
26 | {
27 | boxIcon = MessageBoxIcon.Question;
28 | }
29 | else if (ficon.Glyph == MessageBoxSymbolGlyph.None.ToGlyph())
30 | {
31 | boxIcon = MessageBoxIcon.None;
32 | }
33 | }
34 | return boxIcon;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxIconForegroundConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 | using System.Windows.Media;
6 |
7 | namespace Wpf.Ui.Violeta.Controls;
8 |
9 | [ValueConversion(typeof(MessageBoxIcon), typeof(SolidColorBrush))]
10 | public sealed class MessageBoxIconForegroundConverter : IValueConverter
11 | {
12 | public static MessageBoxIconForegroundConverter New => new();
13 |
14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | if (value is MessageBoxIcon icon)
17 | {
18 | return icon switch
19 | {
20 | MessageBoxIcon.Information => "#55CEF1".ToColor().ToBrush(),
21 | MessageBoxIcon.Success => "#75CD43".ToColor().ToBrush(),
22 | MessageBoxIcon.Warning => "#F9D01A".ToColor().ToBrush(),
23 | MessageBoxIcon.Error => "#FF5656".ToColor().ToBrush(),
24 | MessageBoxIcon.Question => "#55CEF1".ToColor().ToBrush(),
25 | MessageBoxIcon.None or _ => "#55CEF1".ToColor().ToBrush(),
26 | };
27 | }
28 | return "#55CEF1".ToColor().ToBrush();
29 | }
30 |
31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
32 | {
33 | return DependencyProperty.UnsetValue;
34 | }
35 | }
36 |
37 | file static class ColorExtension
38 | {
39 | public static Color ToColor(this string hex) => (Color)ColorConverter.ConvertFromString(hex);
40 |
41 | public static SolidColorBrush ToBrush(this Color color) => new(color);
42 | }
43 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxImageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public static class MessageBoxImageExtensions
7 | {
8 | public static string ToGlyph(this MessageBoxSymbolGlyph symbol)
9 | {
10 | return char.ConvertFromUtf32((int)symbol);
11 | }
12 |
13 | public static MessageBoxSymbolGlyph ToSymbol(this MessageBoxImage image)
14 | {
15 | return image switch
16 | {
17 | MessageBoxImage.Error => MessageBoxSymbolGlyph.Error,
18 | MessageBoxImage.Information => MessageBoxSymbolGlyph.Info,
19 | MessageBoxImage.Warning => MessageBoxSymbolGlyph.Warning,
20 | MessageBoxImage.Question => MessageBoxSymbolGlyph.Question,
21 | MessageBoxImage.None => (MessageBoxSymbolGlyph)0x2007,
22 | _ => throw new NotSupportedException(),
23 | };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxOpenedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public class MessageBoxOpenedEventArgs : RoutedEventArgs
6 | {
7 | internal MessageBoxOpenedEventArgs()
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxSymbolGlyph.cs:
--------------------------------------------------------------------------------
1 | namespace Wpf.Ui.Violeta.Controls;
2 |
3 | public enum MessageBoxSymbolGlyph
4 | {
5 | Info = 0xe946,
6 | Error = 0xe783,
7 | Warning = 0xe7ba,
8 | Question = 0xe9ce,
9 | None = 0x2007,
10 | }
11 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxTemplateSettings.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using Wpf.Ui.Controls;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public class MessageBoxTemplateSettings : DependencyObject
7 | {
8 | internal MessageBoxTemplateSettings()
9 | {
10 | }
11 |
12 | private static readonly DependencyPropertyKey IconElementPropertyKey =
13 | DependencyProperty.RegisterReadOnly(nameof(IconElement), typeof(IconElement), typeof(MessageBoxTemplateSettings), null);
14 |
15 | public static readonly DependencyProperty IconElementProperty = IconElementPropertyKey.DependencyProperty;
16 |
17 | public IconElement IconElement
18 | {
19 | get => (IconElement)GetValue(IconElementProperty);
20 | internal set => SetValue(IconElementPropertyKey, value);
21 | }
22 |
23 | public static readonly DependencyProperty OKButtonTextProperty =
24 | DependencyProperty.Register(nameof(OKButtonText), typeof(string), typeof(MessageBoxTemplateSettings), new PropertyMetadata("OK"));
25 |
26 | public string OKButtonText
27 | {
28 | get => (string)GetValue(OKButtonTextProperty);
29 | set => SetValue(OKButtonTextProperty, value);
30 | }
31 |
32 | public static readonly DependencyProperty YesButtonTextProperty =
33 | DependencyProperty.Register(nameof(YesButtonText), typeof(string), typeof(MessageBoxTemplateSettings), new PropertyMetadata("YES"));
34 |
35 | public string YesButtonText
36 | {
37 | get => (string)GetValue(YesButtonTextProperty);
38 | set => SetValue(YesButtonTextProperty, value);
39 | }
40 |
41 | public static readonly DependencyProperty NoButtonTextProperty =
42 | DependencyProperty.Register(nameof(NoButtonText), typeof(string), typeof(MessageBoxTemplateSettings), new PropertyMetadata("NO"));
43 |
44 | public string NoButtonText
45 | {
46 | get => (string)GetValue(NoButtonTextProperty);
47 | set => SetValue(NoButtonTextProperty, value);
48 | }
49 |
50 | public static readonly DependencyProperty CancelButtonTextProperty =
51 | DependencyProperty.Register(nameof(CancelButtonText), typeof(string), typeof(MessageBoxTemplateSettings), new PropertyMetadata("CANCEL"));
52 |
53 | public string CancelButtonText
54 | {
55 | get => (string)GetValue(CancelButtonTextProperty);
56 | set => SetValue(CancelButtonTextProperty, value);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PendingBox/IPendingHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public interface IPendingHandler : IDisposable
6 | {
7 | public event EventHandler? Closed;
8 |
9 | public event EventHandler? Cancel;
10 |
11 | public DateTime StartTime { get; set; }
12 |
13 | public string? Message { get; set; }
14 |
15 | public bool IsShowCancel { get; set; }
16 |
17 | public bool Canceled { get; }
18 |
19 | public void Show();
20 |
21 | public bool? ShowDialog();
22 |
23 | public void Close();
24 | }
25 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PendingBox/Loading.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PendingBox/PendingBoxDialog.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
16 |
17 |
71 |
72 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PendingBox/PendingHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public class PendingHandler(PendingBoxDialog pendingBoxDialog) : IPendingHandler
7 | {
8 | public PendingBoxDialog Dialog { get; protected set; } = pendingBoxDialog;
9 |
10 | public event EventHandler? Closed;
11 |
12 | public event EventHandler? Cancel;
13 |
14 | public DateTime StartTime { get; set; } = DateTime.Now;
15 |
16 | public virtual string? Message
17 | {
18 | get => Dialog.Dispatcher.Invoke(() => Dialog.Message);
19 | set => Dialog.Dispatcher.Invoke(() => Dialog.Message = value);
20 | }
21 |
22 | public virtual bool IsShowCancel
23 | {
24 | get => Dialog.Dispatcher.Invoke(() => Dialog.IsShowCancel);
25 | set => Dialog.Dispatcher.Invoke(() => Dialog.IsShowCancel = value);
26 | }
27 |
28 | public virtual bool Canceled { get; protected set; } = false;
29 |
30 | public virtual bool CloseOnCanceled { get; set; } = true;
31 |
32 | [SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize")]
33 | [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")]
34 | public virtual void Dispose()
35 | {
36 | try
37 | {
38 | Close();
39 | }
40 | catch
41 | {
42 | ///
43 | }
44 | }
45 |
46 | public virtual void Show()
47 | => Dialog?.Show();
48 |
49 | [Obsolete("Use Show instead")]
50 | public virtual bool? ShowDialog()
51 | => Dialog?.ShowDialog();
52 |
53 | public virtual void Close()
54 | {
55 | Dialog?.Dispatcher.Invoke(() =>
56 | {
57 | if (Dialog != null)
58 | {
59 | Dialog.IsClosedByHandler = true;
60 | Dialog.Close();
61 | }
62 | });
63 | }
64 |
65 | public virtual void RaiseClosedEvent(object? sender, EventArgs e)
66 | => Closed?.Invoke(sender, e);
67 |
68 | public virtual void RaiseCanceledEvent(object? sender, EventArgs e)
69 | {
70 | Cancel?.Invoke(sender, e);
71 | if (CloseOnCanceled)
72 | {
73 | Close();
74 | }
75 | Canceled = true;
76 | }
77 | }
78 |
79 | public class PendingHandlerAsync(PendingBoxDialog pendingBoxDialog) : PendingHandler(pendingBoxDialog);
80 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PersonPicture/PersonPictureAutomationPeer.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Automation.Peers;
2 |
3 | namespace Wpf.Ui.Violeta.Controls;
4 |
5 | public class PersonPictureAutomationPeer(PersonPicture owner) : FrameworkElementAutomationPeer(owner)
6 | {
7 | protected override AutomationControlType GetAutomationControlTypeCore()
8 | {
9 | return AutomationControlType.Text;
10 | }
11 |
12 | protected override string GetClassNameCore()
13 | {
14 | return nameof(PersonPicture);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/PersonPicture/PersonPictureTemplateSettings.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Media;
3 |
4 | namespace Wpf.Ui.Violeta.Controls;
5 |
6 | public sealed class PersonPictureTemplateSettings : DependencyObject
7 | {
8 | #region ActualImageBrush
9 |
10 | private static readonly DependencyPropertyKey ActualImageBrushPropertyKey =
11 | DependencyProperty.RegisterReadOnly(
12 | nameof(ActualImageBrush),
13 | typeof(ImageBrush),
14 | typeof(PersonPictureTemplateSettings),
15 | null);
16 |
17 | public static readonly DependencyProperty ActualImageBrushProperty =
18 | ActualImageBrushPropertyKey.DependencyProperty;
19 |
20 | public ImageBrush ActualImageBrush
21 | {
22 | get => (ImageBrush)GetValue(ActualImageBrushProperty);
23 | internal set => SetValue(ActualImageBrushPropertyKey, value);
24 | }
25 |
26 | #endregion ActualImageBrush
27 |
28 | #region ActualInitials
29 |
30 | private static readonly DependencyPropertyKey ActualInitialsPropertyKey =
31 | DependencyProperty.RegisterReadOnly(
32 | nameof(ActualInitials),
33 | typeof(string),
34 | typeof(PersonPictureTemplateSettings),
35 | new PropertyMetadata(string.Empty));
36 |
37 | public static readonly DependencyProperty ActualInitialsProperty =
38 | ActualInitialsPropertyKey.DependencyProperty;
39 |
40 | public string ActualInitials
41 | {
42 | get => (string)GetValue(ActualInitialsProperty);
43 | internal set => SetValue(ActualInitialsPropertyKey, value);
44 | }
45 |
46 | #endregion ActualInitials
47 | }
48 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/Primitives/IconElementEx.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Data;
6 | using System.Windows.Documents;
7 | using System.Windows.Media;
8 | using Wpf.Ui.Controls;
9 | using Grid = System.Windows.Controls.Grid;
10 |
11 | namespace Wpf.Ui.Violeta.Controls.Primitives;
12 |
13 | [TypeConverter(typeof(IconElementConverter))]
14 | public abstract class IconElementEx : IconElement
15 | {
16 | private protected IconElementEx()
17 | {
18 | }
19 |
20 | protected override void OnForegroundChanged(DependencyPropertyChangedEventArgs args)
21 | {
22 | var baseValueSource = DependencyPropertyHelper.GetValueSource(this, args.Property).BaseValueSource;
23 | _isForegroundDefaultOrInherited = baseValueSource <= BaseValueSource.Inherited;
24 | UpdateShouldInheritForegroundFromVisualParent();
25 | }
26 |
27 | private static readonly DependencyProperty VisualParentForegroundProperty =
28 | DependencyProperty.Register(
29 | nameof(VisualParentForeground),
30 | typeof(Brush),
31 | typeof(IconElementEx),
32 | new PropertyMetadata(null, OnVisualParentForegroundPropertyChanged));
33 |
34 | protected Brush VisualParentForeground
35 | {
36 | get => (Brush)GetValue(VisualParentForegroundProperty);
37 | set => SetValue(VisualParentForegroundProperty, value);
38 | }
39 |
40 | private static void OnVisualParentForegroundPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
41 | {
42 | ((IconElementEx)sender).OnVisualParentForegroundPropertyChanged(args);
43 | }
44 |
45 | private protected virtual void OnVisualParentForegroundPropertyChanged(DependencyPropertyChangedEventArgs args)
46 | {
47 | }
48 |
49 | protected bool ShouldInheritForegroundFromVisualParent
50 | {
51 | get => _shouldInheritForegroundFromVisualParent;
52 | private set
53 | {
54 | if (_shouldInheritForegroundFromVisualParent != value)
55 | {
56 | _shouldInheritForegroundFromVisualParent = value;
57 |
58 | if (_shouldInheritForegroundFromVisualParent)
59 | {
60 | SetBinding(VisualParentForegroundProperty,
61 | new Binding
62 | {
63 | Path = new PropertyPath(TextElement.ForegroundProperty),
64 | Source = VisualParent
65 | });
66 | }
67 | else
68 | {
69 | ClearValue(VisualParentForegroundProperty);
70 | }
71 |
72 | OnShouldInheritForegroundFromVisualParentChanged();
73 | }
74 | }
75 | }
76 |
77 | private protected virtual void OnShouldInheritForegroundFromVisualParentChanged()
78 | {
79 | }
80 |
81 | private void UpdateShouldInheritForegroundFromVisualParent()
82 | {
83 | ShouldInheritForegroundFromVisualParent =
84 | _isForegroundDefaultOrInherited &&
85 | Parent != null &&
86 | VisualParent != null &&
87 | Parent != VisualParent;
88 | }
89 |
90 | protected UIElementCollection Children
91 | {
92 | get
93 | {
94 | EnsureLayoutRoot();
95 | return _layoutRoot.Children;
96 | }
97 | }
98 |
99 | protected override int VisualChildrenCount => 1;
100 |
101 | protected override Visual GetVisualChild(int index)
102 | {
103 | if (index == 0)
104 | {
105 | EnsureLayoutRoot();
106 | return _layoutRoot;
107 | }
108 | else
109 | {
110 | throw new ArgumentOutOfRangeException(nameof(index));
111 | }
112 | }
113 |
114 | protected override Size MeasureOverride(Size availableSize)
115 | {
116 | EnsureLayoutRoot();
117 | _layoutRoot.Measure(availableSize);
118 | return _layoutRoot.DesiredSize;
119 | }
120 |
121 | protected override Size ArrangeOverride(Size finalSize)
122 | {
123 | EnsureLayoutRoot();
124 | _layoutRoot.Arrange(new Rect(new Point(), finalSize));
125 | return finalSize;
126 | }
127 |
128 | protected override void OnVisualParentChanged(DependencyObject oldParent)
129 | {
130 | base.OnVisualParentChanged(oldParent);
131 | UpdateShouldInheritForegroundFromVisualParent();
132 | }
133 |
134 | private void EnsureLayoutRoot()
135 | {
136 | if (_layoutRoot != null)
137 | return;
138 |
139 | _layoutRoot = new Grid
140 | {
141 | Background = Brushes.Transparent,
142 | SnapsToDevicePixels = true,
143 | };
144 | _ = InitializeChildren();
145 |
146 | AddVisualChild(_layoutRoot);
147 | }
148 |
149 | private Grid _layoutRoot = null!;
150 | private bool _isForegroundDefaultOrInherited = true;
151 | private bool _shouldInheritForegroundFromVisualParent;
152 | }
153 |
--------------------------------------------------------------------------------
/src/Wpf.Ui.Violeta/Controls/Primitives/MenuItemGroup.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 |
5 | namespace Wpf.Ui.Violeta.Controls.Primitives;
6 |
7 | public class MenuItemGroup : List