Refactored resource file management..

Remove use of resx file
Resource files are marked as "Resource" instead
Resources are loaded via resource dictionaries (either from code behind or XAML directly through binding)
Reduce amount of required image conversions

Also fix overlay of 50 percent transparency (is now rendered half transparent correct instead of adding "white fog")
Remove obsolete image as rendering is done directly in code

Also fix rendering of link indicator at correct image position

Also remove setting Icons of all Windows (as default is application icon anyway)
This commit is contained in:
Peter Kirmeier 2023-08-12 02:33:11 +02:00
parent 7cbad2bbdc
commit de483c874c
25 changed files with 159 additions and 483 deletions

View file

@ -1,7 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2022-2022 Peter Kirmeier -->
<!-- Copyright (c) 2022-2023 Peter Kirmeier -->
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SystemTrayMenu"
x:Class="SystemTrayMenu.App" ShutdownMode="OnExplicitShutdown" />
x:Class="SystemTrayMenu.App" ShutdownMode="OnExplicitShutdown">
<Application.Resources>
<BitmapImage x:Key="ApplicationImage" UriSource="../Resources/SystemTrayMenu.png" />
<BitmapImage x:Key="ApplicationIconImage" UriSource="../Resources/SystemTrayMenu.ico" />
<BitmapImage x:Key="LinkArrowIconImage" UriSource="../Resources/LinkArrow.ico" />
<BitmapImage x:Key="LoadingIconImage" UriSource="../Resources/Loading.ico" />
<BitmapImage x:Key="NotFoundIconImage" UriSource="../Resources/NotFound.ico" />
</Application.Resources>
</Application>

View file

@ -5,6 +5,8 @@
namespace SystemTrayMenu
{
using System;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Threading;
using SystemTrayMenu.Business;
@ -18,18 +20,21 @@ namespace SystemTrayMenu
/// </summary>
public partial class App : Application, IDisposable
{
private readonly Menus menus = new();
private Menus? menus;
private JoystickHelper? joystickHelper;
private bool isDisposed;
public App()
{
InitializeComponent();
AppRestart.BeforeRestarting += Dispose;
Activated += (_, _) => IsActiveApp = true;
Deactivated += (_, _) => IsActiveApp = false;
Startup += (_, _) =>
{
menus = new();
menus.Startup();
if (Settings.Default.SupportGamepad)
@ -55,17 +60,35 @@ namespace SystemTrayMenu
GC.SuppressFinalize(this);
}
/// <summary>
/// Loads an Icon from the application's Resources.
/// Note: Only allowed to be called after App's Startup event.
/// </summary>
/// <param name="resourceName">Absolute file path from root directory.</param>
/// <returns>New Icon object.</returns>
internal static Icon LoadIconFromResource(string resourceName)
{
using (Stream stream = GetResourceStream(new("pack://application:,,,/" + resourceName, UriKind.Absolute)).Stream)
{
return new(stream);
}
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
if (joystickHelper != null)
{
joystickHelper.KeyPressed -= menus.KeyPressed;
if (menus != null)
{
joystickHelper.KeyPressed -= menus.KeyPressed;
}
joystickHelper.Dispose();
}
menus.Dispose();
menus?.Dispose();
isDisposed = true;
}

View file

@ -44,7 +44,6 @@ namespace SystemTrayMenu
FolderOptions.Initialize();
using App app = new ();
app.InitializeComponent();
isStartup = false;
Log.WriteApplicationRuns();
app.Run();

View file

@ -12,13 +12,12 @@ namespace SystemTrayMenu
using SystemTrayMenu.Properties;
using SystemTrayMenu.UserInterface.FolderBrowseDialog;
using SystemTrayMenu.Utilities;
using static SystemTrayMenu.Utilities.IconReader;
using Icon = System.Drawing.Icon;
public static class Config
{
private static readonly Icon SystemTrayMenu = new(Properties.Resources.SystemTrayMenu, (int)SystemParameters.SmallIconWidth, (int)SystemParameters.SmallIconHeight);
private static readonly Icon? IconRootFolder = GetIconAsIcon(Path, Path, false, true, IconSize.Small);
private static readonly Icon? IconRootFolder = IconReader.GetRootFolderIcon(Path);
private static Icon? applicationIcon;
private static bool readDarkModeDone;
private static bool isDarkMode;
@ -70,7 +69,14 @@ namespace SystemTrayMenu
}
else
{
return SystemTrayMenu;
if (applicationIcon == null)
{
Icon icon = App.LoadIconFromResource("Resources/SystemTrayMenu.ico");
applicationIcon = new(icon, (int)SystemParameters.SmallIconWidth, (int)SystemParameters.SmallIconHeight);
icon.Dispose();
}
return applicationIcon;
}
}

View file

@ -219,7 +219,7 @@ namespace SystemTrayMenu.DataClasses
if (!cacheHit)
{
IconLoading = true;
ColumnIcon = SystemTrayMenu.Resources.StaticResources.LoadingImgSrc; // TODO: Maybe add rotation animation like for the loading Menu icon? (See: pictureBoxLoading, LoadingRotation)
ColumnIcon = (ImageSource?)Application.Current.Resources["LoadingIconImage"]; // TODO: Maybe add rotation animation like for the loading Menu icon? (See: pictureBoxLoading, LoadingRotation)
}
}
@ -343,11 +343,11 @@ namespace SystemTrayMenu.DataClasses
{
if (icon == null)
{
icon = Resources.NotFound.ToBitmapSource();
icon = (BitmapSource)Application.Current.Resources["NotFoundIconImage"];
}
else if (HiddenEntry)
{
icon = ImagingHelper.CreateIconWithOverlay(icon, Resources.White50Percentage.ToBitmapSource());
icon = ImagingHelper.ApplyOpactiy(icon, 0.5d);
icon?.Freeze(); // Make it accessible for any thread
}

View file

@ -115,18 +115,44 @@ namespace SystemTrayMenu.Helpers
return ConvertToIcon(inputStream, outputStream, size, preserveAspectRatio);
}
/// <summary>
/// Renders an image on top of an image.
/// </summary>
/// <param name="originalBitmap">Base image (remains unchanged).</param>
/// <param name="overlayBitmap">Image on top (remains unchanged).</param>
/// <returns>Rendered image.</returns>
internal static RenderTargetBitmap CreateIconWithOverlay(BitmapSource originalBitmap, BitmapSource overlayBitmap)
{
DrawingVisual dVisual = new DrawingVisual();
DrawingVisual dVisual = new ();
using (DrawingContext dc = dVisual.RenderOpen())
{
dc.DrawImage(originalBitmap, new (0, 0, originalBitmap.PixelWidth, originalBitmap.PixelHeight));
dc.DrawImage(overlayBitmap, new (0, 0, overlayBitmap.PixelWidth, overlayBitmap.PixelHeight));
dc.DrawImage(overlayBitmap, new (0, 0, originalBitmap.PixelWidth, originalBitmap.PixelHeight));
}
RenderTargetBitmap targetBitmap = new (originalBitmap.PixelWidth, originalBitmap.PixelHeight, originalBitmap.DpiX, originalBitmap.DpiY, PixelFormats.Default);
targetBitmap.Render(dVisual);
return targetBitmap;
}
/// <summary>
/// Sets a flat the alpha channel value for an image.
/// </summary>
/// <param name="originalBitmap">Base image (remains unchanged).</param>
/// <param name="opacity">Opacity value.</param>
/// <returns>Rendered image.</returns>
internal static RenderTargetBitmap ApplyOpactiy(BitmapSource originalBitmap, double opacity)
{
DrawingVisual dVisual = new ();
using (DrawingContext dc = dVisual.RenderOpen())
{
dc.PushOpacity(opacity);
dc.DrawImage(originalBitmap, new(0, 0, originalBitmap.PixelWidth, originalBitmap.PixelHeight));
}
RenderTargetBitmap targetBitmap = new(originalBitmap.PixelWidth, originalBitmap.PixelHeight, originalBitmap.DpiX, originalBitmap.DpiY, PixelFormats.Default);
targetBitmap.Render(dVisual);
return targetBitmap;
}
}
}

View file

@ -1,113 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SystemTrayMenu.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SystemTrayMenu.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
public static System.Drawing.Icon LinkArrow {
get {
object obj = ResourceManager.GetObject("LinkArrow", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
public static System.Drawing.Icon Loading {
get {
object obj = ResourceManager.GetObject("Loading", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
public static System.Drawing.Icon NotFound {
get {
object obj = ResourceManager.GetObject("NotFound", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
public static System.Drawing.Icon SystemTrayMenu {
get {
object obj = ResourceManager.GetObject("SystemTrayMenu", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
public static System.Drawing.Icon White50Percentage {
get {
object obj = ResourceManager.GetObject("White50Percentage", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

View file

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="White50Percentage" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\White50Percentage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SystemTrayMenu" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\SystemTrayMenu.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="NotFound" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\NotFound.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Loading" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Loading.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="LinkArrow" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\LinkArrow.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View file

@ -1,85 +0,0 @@
// <copyright file="StaticResources.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Resources
{
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using SystemTrayMenu.Utilities;
internal static class StaticResources
{
internal static readonly Icon LoadingIcon = Properties.Resources.Loading;
private static readonly object ApplicationImgSrcLock = new();
private static readonly object LoadingImgSrcLock = new ();
private static ImageSource? applicationgImgSrc;
private static ImageSource? loadingImgSrc;
public static ImageSource? ApplicationImgSrc
{
get
{
if (applicationgImgSrc == null)
{
lock (ApplicationImgSrcLock)
{
if (applicationgImgSrc == null)
{
applicationgImgSrc = LoadFromAssemblyManifestResources("Resources.SystemTrayMenu.png");
applicationgImgSrc?.Freeze(); // Make it accessible for any thread
}
}
}
return applicationgImgSrc;
}
}
public static ImageSource LoadingImgSrc
{
get
{
if (loadingImgSrc == null)
{
lock (LoadingImgSrcLock)
{
if (loadingImgSrc == null)
{
loadingImgSrc = Properties.Resources.Loading.ToBitmapSource();
loadingImgSrc.Freeze(); // Make it accessible for any thread
}
}
}
return loadingImgSrc;
}
}
private static ImageSource? LoadFromAssemblyManifestResources(string name)
{
Assembly myassembly = Assembly.GetExecutingAssembly();
string myname = myassembly.GetName().Name ?? string.Empty;
using (Stream? imgstream = myassembly.GetManifestResourceStream(myname + "." + name))
{
if (imgstream != null)
{
BitmapImage imageSource = new();
imageSource.BeginInit();
imageSource.StreamSource = imgstream;
imageSource.EndInit();
return imageSource;
}
}
return null;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -126,32 +126,23 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Resources\SystemTrayMenu.ico" />
<None Remove="Resources\HowToOpenSettings.png" />
<Resource Include="Resources\SystemTrayMenu.ico" />
<Resource Include="Resources\SystemTrayMenu.png" />
<Resource Include="Resources\LinkArrow.ico" />
<Resource Include="Resources\Loading.ico" />
<Resource Include="Resources\HowToOpenSettings.png" />
<None Remove="Resources\SystemTrayMenu.png" />
<EmbeddedResource Include="Resources\SystemTrayMenu.png" />
<Resource Include="Resources\NotFound.ico" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">

View file

@ -1,16 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2022-2022 Peter Kirmeier -->
<!-- Copyright (c) 2022-2023 Peter Kirmeier -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="clr-namespace:SystemTrayMenu.Utilities"
xmlns:local="clr-namespace:SystemTrayMenu.UserInterface"
x:Class="SystemTrayMenu.UserInterface.AboutBox"
mc:Ignorable="d" Title="{u:Translate 'About SystemTrayMenu'}" Height="513" Width="418" ResizeMode="NoResize" SizeToContent="Height">
<DockPanel>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" DockPanel.Dock="Top">
<Image x:Name="ImagePictureBox" Width="32" Height="32" Margin="3" />
<Image Width="32" Height="32" Margin="3" Source="{StaticResource ApplicationImage}" />
<StackPanel VerticalAlignment="Center">
<Label x:Name="AppTitleLabel" Content="%title%" Padding="0" Margin="3"/>
<Label x:Name="AppDescriptionLabel" Content="%description%" Padding="0" Margin="3"/>

View file

@ -20,7 +20,6 @@ namespace SystemTrayMenu.UserInterface
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Win32;
using SystemTrayMenu.Resources;
using SystemTrayMenu.Utilities;
/// <summary>
@ -39,9 +38,6 @@ namespace SystemTrayMenu.UserInterface
{
InitializeComponent();
ImagePictureBox.Source = StaticResources.ApplicationImgSrc;
Icon = StaticResources.ApplicationImgSrc;
Loaded += AboutBox_Load;
TabPanelDetails.Visibility = Visibility.Collapsed;

View file

@ -5,6 +5,7 @@
namespace SystemTrayMenu.UserInterface
{
using System;
using System.Drawing;
using System.Windows.Threading;
using H.NotifyIcon.Core;
@ -12,6 +13,7 @@ namespace SystemTrayMenu.UserInterface
{
private readonly Dispatcher dispatchter = Dispatcher.CurrentDispatcher;
private readonly TrayIconWithContextMenu notifyIcon = new ();
private Icon? loadingIcon;
public AppNotifyIcon()
{
@ -35,11 +37,13 @@ namespace SystemTrayMenu.UserInterface
{
notifyIcon.Icon = IntPtr.Zero;
notifyIcon.Dispose();
loadingIcon?.Dispose();
}
public void LoadingStart()
{
notifyIcon.Icon = Resources.StaticResources.LoadingIcon.Handle;
loadingIcon ??= App.LoadIconFromResource("Resources/Loading.ico");
notifyIcon.Icon = loadingIcon.Handle;
}
public void LoadingStop()

View file

@ -7,6 +7,7 @@
xmlns:u="clr-namespace:SystemTrayMenu.Utilities"
x:Class="SystemTrayMenu.UserInterface.HowToOpenSettingsWindow"
mc:Ignorable="d" Title="{u:Translate 'Hint'}" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" WindowStyle="SingleBorderWindow" ShowInTaskbar="True" ResizeMode="NoResize">
<Window.Resources>
<BitmapImage x:Key="HowToOpenSettingsImage" UriSource="../Resources/HowToOpenSettings.png" />
</Window.Resources>
@ -19,7 +20,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="5" MaxWidth="{Binding ElementName=image, Path=ActualWidth}" x:Name="label" TextWrapping="Wrap" Text="{u:Translate 'The settings menu can also be opened by right-clicking the icon in the system tray at the bottom right, in case you can no longer open it via the menu.'}" />
<TextBlock Grid.Row="0" Margin="5" MinWidth="200" MaxWidth="{Binding ElementName=image, Path=ActualWidth}" x:Name="label" TextWrapping="Wrap" Text="{u:Translate 'The settings menu can also be opened by right-clicking the icon in the system tray at the bottom right, in case you can no longer open it via the menu.'}" />
<Image Grid.Row="1" Margin="10" x:Name="image" Source="{StaticResource HowToOpenSettingsImage}" Stretch="None"/>
<CheckBox Grid.Row="2" Margin="5" x:Name="checkBoxDontShowThisHintAgain" Content="???" />

View file

@ -7,7 +7,6 @@
namespace SystemTrayMenu.UserInterface
{
using System.Windows;
using SystemTrayMenu.Resources;
using SystemTrayMenu.Utilities;
/// <summary>
@ -19,8 +18,6 @@ namespace SystemTrayMenu.UserInterface
{
InitializeComponent();
Icon = StaticResources.ApplicationImgSrc;
// TODO: Find a way to escape ' within inline single quotes markup string in XAML
checkBoxDontShowThisHintAgain.Content = Translator.GetText("Don't show this hint again.");
}

View file

@ -7,6 +7,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:stm="clr-namespace:SystemTrayMenu"
xmlns:u="clr-namespace:SystemTrayMenu.Utilities"
xmlns:local="clr-namespace:SystemTrayMenu.UserInterface"
x:Class="SystemTrayMenu.UserInterface.Menu"
mc:Ignorable="d" ResizeMode="NoResize" WindowStyle="None" Topmost="True" Background="Transparent" AllowsTransparency="True" SizeToContent="WidthAndHeight" ShowInTaskbar="False" SnapsToDevicePixels="True" UseLayoutRounding="True"
@ -135,7 +136,7 @@
</Setter>
</Style>
</DockPanel.Resources>
<Label x:Name="labelStatus" Content="0 items" Padding="0" DockPanel.Dock="Left" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{x:Static stm:MenuDefines.ColorIcons}"/>
<Label x:Name="labelStatus" Content="{u:Translate 'loading'}" Padding="0" DockPanel.Dock="Left" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{x:Static stm:MenuDefines.ColorIcons}"/>
<Image x:Name="pictureBoxLoading" Width="18" Height="18" DockPanel.Dock="Right"
HorizontalAlignment="Right" RenderTransformOrigin="0.5,0.5" d:Visibility="Visible" Visibility="Collapsed">

View file

@ -98,7 +98,7 @@ namespace SystemTrayMenu.UserInterface
if (ParentMenu == null)
{
// Should never happen as each parent menu must have a valid entry which's owner is set
throw new ArgumentNullException(new (nameof(ParentMenu)));
throw new ArgumentNullException(new (nameof(ParentMenu)));
}
Level = RowDataParent.Level + 1;
@ -108,11 +108,6 @@ namespace SystemTrayMenu.UserInterface
buttonOpenFolder.Visibility = Visibility.Collapsed;
buttonSettings.Visibility = Visibility.Collapsed;
buttonRestart.Visibility = Visibility.Collapsed;
labelStatus.Content = Translator.GetText("loading");
// Todo: use embedded resources that we can assign image in XAML already
pictureBoxLoading.Source = SystemTrayMenu.Resources.StaticResources.LoadingImgSrc;
pictureBoxLoading.Visibility = Visibility.Visible;
}

View file

@ -15,7 +15,6 @@ namespace SystemTrayMenu.UserInterface
using Microsoft.Win32;
using SystemTrayMenu.Helpers;
using SystemTrayMenu.Properties;
using SystemTrayMenu.Resources;
using SystemTrayMenu.UserInterface.FolderBrowseDialog;
using SystemTrayMenu.Utilities;
using Windows.ApplicationModel;
@ -34,8 +33,6 @@ namespace SystemTrayMenu.UserInterface
{
InitializeComponent();
Icon = StaticResources.ApplicationImgSrc;
PreviewKeyDown += HandlePreviewKeyDown;
Translate();

View file

@ -6,5 +6,5 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="SystemTrayMenu.UserInterface.TaskbarLogo"
mc:Ignorable="d" WindowStyle="None" ResizeMode="NoResize" Width="38" Height="38" Topmost="True" Background="Transparent" AllowsTransparency="True">
<Image x:Name="ImagePictureBox" Width="32" Height="32" Margin="3"/>
<Image Width="32" Height="32" Margin="3" Source="{StaticResource ApplicationImage}"/>
</Window>

View file

@ -2,16 +2,13 @@
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
//
// Copyright (c) 2022-2022 Peter Kirmeier
// Copyright (c) 2022-2023 Peter Kirmeier
namespace SystemTrayMenu.UserInterface
{
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using SystemTrayMenu.Resources;
/// <summary>
/// Logic of Taskbar window.
@ -24,13 +21,6 @@ namespace SystemTrayMenu.UserInterface
Assembly myassembly = Assembly.GetExecutingAssembly();
string myname = myassembly.GetName().Name ?? string.Empty;
ImagePictureBox.Source = StaticResources.ApplicationImgSrc;
Icon = Imaging.CreateBitmapSourceFromHIcon(
Config.GetAppIcon().Handle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
Title = myname;
Closed += (_, _) => Application.Current.Shutdown();

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2022-2022 Peter Kirmeier -->
<!-- Copyright (c) 2022-2023 Peter Kirmeier -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="clr-namespace:SystemTrayMenu.Utilities"
xmlns:local="clr-namespace:SystemTrayMenu.UserInterface"
x:Class="SystemTrayMenu.UserInterface.UpdateWindow"
mc:Ignorable="d" Title="{u:Translate 'New version available!'}" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" MinWidth="600" MinHeight="400" WindowStyle="ToolWindow" ShowInTaskbar="False">
<Grid>

View file

@ -6,12 +6,8 @@
namespace SystemTrayMenu.UserInterface
{
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Media.Imaging;
using SystemTrayMenu.Helpers.Updater;
using SystemTrayMenu.Resources;
/// <summary>
/// Logic of Update window.
@ -22,8 +18,6 @@ namespace SystemTrayMenu.UserInterface
{
InitializeComponent();
Icon = StaticResources.ApplicationImgSrc;
label.Content = ((string)label.Content) + " " + GitHubUpdate.LatestVersionName;
}

View file

@ -9,12 +9,14 @@ namespace SystemTrayMenu.Utilities
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using SystemTrayMenu.DllImports;
using SystemTrayMenu.Helpers;
/// <summary>
/// Provides static methods to read system icons for folders and files.
@ -23,12 +25,7 @@ namespace SystemTrayMenu.Utilities
{
private static readonly ConcurrentDictionary<string, BitmapSource> IconDictPersistent = new();
private static readonly ConcurrentDictionary<string, BitmapSource> IconDictCache = new();
internal enum IconSize
{
Large = 0, // 32x32 pixels
Small = 1, // 16x16 pixels
}
private static readonly BitmapSource? OverlayImage = (BitmapSource?)Application.Current.Resources["LinkArrowIconImage"];
// see https://github.com/Hofknecht/SystemTrayMenu/issues/209.
internal static bool IsPreloading { get; set; } = true;
@ -52,7 +49,6 @@ namespace SystemTrayMenu.Utilities
Action<BitmapSource?> onIconLoaded)
{
bool cacheHit;
BitmapSource? icon;
string key;
string extension = Path.GetExtension(path);
if (IsExtensionWithSameIcon(extension))
@ -64,7 +60,7 @@ namespace SystemTrayMenu.Utilities
key = path;
}
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out BitmapSource? icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
cacheHit = false;
@ -97,9 +93,8 @@ namespace SystemTrayMenu.Utilities
Action<BitmapSource?> onIconLoaded)
{
bool cacheHit;
BitmapSource? icon;
string key = path;
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out BitmapSource? icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
cacheHit = false;
@ -134,83 +129,74 @@ namespace SystemTrayMenu.Utilities
return cacheHit;
}
internal static Icon? GetIconAsIcon(string path, string resolvedPath, bool linkOverlay, bool isFolder, IconSize? forceSize = null)
internal static Icon? GetRootFolderIcon(string path)
{
IconSize size;
if (forceSize.HasValue)
{
size = forceSize.Value;
}
else if (Scaling.Factor >= 1.25f ||
Scaling.FactorByDpi >= 1.25f ||
Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f)
{
// IconSize.Large returns another folder icon than windows explorer
size = IconSize.Large;
}
else
{
size = IconSize.Small;
}
NativeMethods.SHFILEINFO shFileInfo = default;
bool linkOverlay = false;
bool largeIcon = false;
uint flags = GetFlags(linkOverlay, largeIcon);
uint attribute = NativeMethods.FileAttributeDirectory;
IntPtr imageList = NativeMethods.Shell32SHGetFileInfo(path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags);
return GetIcon(path, linkOverlay, shFileInfo, imageList);
}
internal static BitmapSource? TryGetIconAsBitmapSource(string path, string resolvedPath, bool linkOverlay, bool isFolder)
{
BitmapSource? result = null;
Icon? icon;
if (Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
if (!isFolder && Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(path);
if (icon != null)
{
result = CreateBitmapSourceFromIcon(icon);
icon.Dispose();
}
}
else if (File.Exists(resolvedPath) &&
else if (!isFolder && File.Exists(resolvedPath) &&
Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(resolvedPath);
if (linkOverlay && icon != null)
if (icon != null)
{
icon = AddIconOverlay(icon, Properties.Resources.LinkArrow);
result = CreateBitmapSourceFromIcon(icon);
icon.Dispose();
if (linkOverlay && OverlayImage != null)
{
result = ImagingHelper.CreateIconWithOverlay(result, OverlayImage);
}
}
}
else
{
NativeMethods.SHFILEINFO shFileInfo = default;
uint flags = GetFlags(linkOverlay, size);
bool largeIcon = // Note: large returns another folder icon than windows explorer
Scaling.Factor >= 1.25f ||
Scaling.FactorByDpi >= 1.25f ||
Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f;
uint flags = GetFlags(linkOverlay, largeIcon);
uint attribute = isFolder ? NativeMethods.FileAttributeDirectory : NativeMethods.FileAttributeNormal;
IntPtr imageList = NativeMethods.Shell32SHGetFileInfo(path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags);
icon = GetIcon(path, linkOverlay, shFileInfo, imageList);
if (icon != null)
{
result = CreateBitmapSourceFromIcon(icon);
icon.Dispose();
}
}
return icon;
return result;
}
private static BitmapSource GetIconAsBitmapSource(string path, string resolvedPath, bool linkOverlay, bool isFolder)
{
Icon? icon = GetIconAsIcon(path, resolvedPath, linkOverlay, isFolder, null);
BitmapSource bitmapSource;
if (icon != null)
{
bitmapSource = icon.ToBitmapSource();
icon.Dispose();
}
else
{
bitmapSource = Properties.Resources.NotFound.ToBitmapSource();
}
BitmapSource? bitmapSource = TryGetIconAsBitmapSource(path, resolvedPath, linkOverlay, isFolder);
bitmapSource ??= (BitmapSource)Application.Current.Resources["NotFoundIconImage"];
bitmapSource.Freeze(); // Make it accessible for any thread
return bitmapSource;
}
private static Icon AddIconOverlay(Icon originalIcon, Icon overlay)
{
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
Icon icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
return icon;
}
private static ConcurrentDictionary<string, BitmapSource> DictIconCache(bool checkPersistentFirst)
=> checkPersistentFirst ? IconDictPersistent : IconDictCache;
@ -226,7 +212,7 @@ namespace SystemTrayMenu.Utilities
return isExtensionWithSameIcon;
}
private static uint GetFlags(bool linkOverlay, IconSize size)
private static uint GetFlags(bool linkOverlay, bool largeIcon)
{
uint flags = NativeMethods.ShgfiIcon | NativeMethods.ShgfiSYSICONINDEX;
if (linkOverlay)
@ -234,7 +220,7 @@ namespace SystemTrayMenu.Utilities
flags += NativeMethods.ShgfiLINKOVERLAY;
}
if (size == IconSize.Small)
if (!largeIcon)
{
flags += NativeMethods.ShgfiSMALLICON;
}
@ -279,13 +265,22 @@ namespace SystemTrayMenu.Utilities
if (!linkOverlay)
{
NativeMethods.User32DestroyIcon(hIcon);
_ = NativeMethods.User32DestroyIcon(hIcon);
}
NativeMethods.User32DestroyIcon(shFileInfo.hIcon);
_ = NativeMethods.User32DestroyIcon(shFileInfo.hIcon);
}
return icon;
}
private static BitmapSource CreateBitmapSourceFromIcon(Icon icon)
{
return (BitmapSource)new IconToImageSourceConverter().Convert(
icon,
typeof(BitmapSource),
null!,
CultureInfo.InvariantCulture);
}
}
}

View file

@ -7,13 +7,10 @@
namespace SystemTrayMenu.Utilities
{
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Icon = System.Drawing.Icon;
internal static class WPFExtensions
{
@ -74,15 +71,5 @@ namespace SystemTrayMenu.Utilities
{
return child == null ? new() : child.TransformToAncestor(parent).Transform(new ());
}
// TODO: Find and remove any unnecessary convertions
internal static BitmapSource ToBitmapSource(this Icon icon)
{
return (BitmapSource)new IconToImageSourceConverter().Convert(
icon,
typeof(BitmapSource),
null!,
CultureInfo.InvariantCulture);
}
}
}