工具路径为exe时默认显示exe自身图标

ToolItem新增IsExePath计算属性,ExePathToIconConverter通过Icon.ExtractAssociatedIcon提取exe图标并缓存,卡片模板图标区改为三层优先级:FontAwesome > exe图标 > 首字母
This commit is contained in:
2026-05-10 14:26:55 +08:00
parent f33c89d2c4
commit e126184973
4 changed files with 86 additions and 3 deletions

View File

@@ -62,4 +62,12 @@ public class ToolItem
/// 是否在软件启动时自动运行 /// 是否在软件启动时自动运行
/// </summary> /// </summary>
public bool AutoRunOnStart { get; set; } public bool AutoRunOnStart { get; set; }
/// <summary>
/// 路径是否指向 .exe 文件(仅运行时判断,不存入 JSON
/// </summary>
[JsonIgnore]
public bool IsExePath =>
!IsGroup && !string.IsNullOrWhiteSpace(ExecutablePath) &&
ExecutablePath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -17,6 +17,7 @@
<local:BoolToOpacityConverter x:Key="BoolToOpacityConverter"/> <local:BoolToOpacityConverter x:Key="BoolToOpacityConverter"/>
<local:FirstCharConverter x:Key="FirstCharConverter"/> <local:FirstCharConverter x:Key="FirstCharConverter"/>
<local:StringToIconCharConverter x:Key="StringToIconCharConverter"/> <local:StringToIconCharConverter x:Key="StringToIconCharConverter"/>
<local:ExePathToIconConverter x:Key="ExePathToIconConverter"/>
<!-- 工具卡片模板 --> <!-- 工具卡片模板 -->
<DataTemplate x:Key="ToolCardTemplate" DataType="{x:Type models:ToolItem}"> <DataTemplate x:Key="ToolCardTemplate" DataType="{x:Type models:ToolItem}">
@@ -93,7 +94,26 @@
</Style> </Style>
</fa:IconBlock.Style> </fa:IconBlock.Style>
</fa:IconBlock> </fa:IconBlock>
<!-- 首字母回退IconCode 为空时显示) --> <!-- exe 图标回退IconCode为空且为exe时显示) -->
<Image Source="{Binding ExecutablePath, Converter={StaticResource ExePathToIconConverter}}"
Width="28" Height="28"
HorizontalAlignment="Center">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IconCode}" Value=""/>
<Condition Binding="{Binding IsExePath}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<!-- 首字母回退IconCode为空且非exe时显示 -->
<TextBlock FontSize="28" <TextBlock FontSize="28"
Foreground="{DynamicResource Theme.Accent}" Foreground="{DynamicResource Theme.Accent}"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
@@ -102,9 +122,13 @@
<Setter Property="Visibility" Value="Collapsed"/> <Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Text" Value="{Binding Name, Converter={StaticResource FirstCharConverter}}"/> <Setter Property="Text" Value="{Binding Name, Converter={StaticResource FirstCharConverter}}"/>
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding IconCode}" Value=""> <MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IconCode}" Value=""/>
<Condition Binding="{Binding IsExePath}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/> <Setter Property="Visibility" Value="Visible"/>
</DataTrigger> </MultiDataTrigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
</TextBlock.Style> </TextBlock.Style>

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
@@ -229,3 +230,53 @@ public class StringToIconCharConverter : IValueConverter
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException(); => throw new NotImplementedException();
} }
/// <summary>
/// string (exe路径) → ImageSource: 提取 .exe 文件自身图标,带缓存
/// </summary>
public class ExePathToIconConverter : IValueConverter
{
private static readonly ConcurrentDictionary<string, ImageSource?> Cache = new();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not string path || path.Length == 0)
return null;
if (!path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
return null;
return Cache.GetOrAdd(path, key =>
{
try
{
if (!File.Exists(key))
return null;
using var icon = System.Drawing.Icon.ExtractAssociatedIcon(key);
if (icon == null)
return null;
using var bitmap = icon.ToBitmap();
using var ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
catch
{
return null;
}
});
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}