Pull to refresh

Использование стандартных иконок Windows в WPF

При использовании фреймворка WPF может возникнуть необходимость использовать стандартные иконки Windows (Error, Warning, Information и так далее). У разработчиков WinForms есть такая возможность, но в WPF такой возможности нет. Зачем это нужно? Использование системных иконок поможет соблюсти look and feel той версии Windows, на которой запускается программа, и позволит (при желании) сделать свои версии стандартных диалоговых окон (на WindowsXP, например, нет возможности использовать Windows CodePack (ранее — Vista Bridge), с его замечательным Task Dialog.
Приведённый ниже код предназначен для работы в среде .NET 3.5 SP1.

Определимся с основными моментами


Для начала, необходимо создать перечисление, которое позволит нам идентифицировать стандартные иконки в коде.

copy to clipboardподсветка кода
  1. public enum SysIconEnum  
  2. {  
  3.     Unknown,  
  4.     Application,  
  5.     Asterisk,  
  6.     Error,  
  7.     Exclamation,  
  8.     Hand,  
  9.     Information,  
  10.     Question,  
  11.     Shield,  
  12.     Warning,  
  13.     WinLogo  
  14. }  


Как видно, члены перечисления соответствуют статическим свойствам класса System.Drawing.SysIcons

Теперь нужно создать метод перевода иконок из типа System.Drawing.Icon в тип System.Windows.Media.Imaging.BitmapSource, который «понимает» (в отличие от System.Drawing.Icon) WPF. Это несложно, учитывая, что необходимые средства уже есть.
copy to clipboardподсветка кода
  1. /// <summary>  
  2. /// Кеш изображений.  
  3. /// </summary>  
  4. private static Dictionary<SysIconEnum, BitmapSource> Images =   
  5.                new Dictionary<SysIconEnum, BitmapSource>();  
  6.   
  7. /// <summary>  
  8. /// Загрузить иконку.  
  9. /// </summary>  
  10. /// <param name=«Icon»>Иконка.</param>  
  11. /// <returns>Изображение.</returns>  
  12. internal static BitmapSource LoadIcon(SysIconEnum Icon)  
  13. {  
  14.     lock (Images)  
  15.     {  
  16.         if (!Images.ContainsKey(Icon))  
  17.         {  
  18.             Icon icon = GetIcon(Icon);  
  19.             if (icon != null)  
  20.             {  
  21.                 BitmapSource res = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(  
  22.     icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());  
  23.                 res.Freeze();  
  24.                 Images[Icon] = res;  
  25.             }  
  26.             else Images[Icon] = null;  
  27.         }  
  28.         return Images[Icon];  
  29.     }  
  30. }  


Чего ещё не хватает для полного счастья? На самом деле, для того, чтобы полученные иконки можно было удобно использовать в XAML, можно реализовать несколько способов доступа к ним: новую схему URI (sysicon://); включить их в автоматически подгружаемый ресурс (Themes/Generic.xaml), присвоив им объявленные в статическом классе ключи типа ComponentResourceKey (это позволить впоследствии пользователям библиотеки «подменить» иконки на любые другие, переопределив их в пользовательских ресурсах App.xaml); использовать статические свойства (получаем доступ к ним при помощи {x:Static ...}); определить собственное расширение XAML-разметки. После некоторых размышлений, я решил новую схему URI не реализовывать (в первую очередь по причине того, что её необходимо регистрировать явным образом через вызов метода, а это противоречит декларативной природе WPF). Ниже рассмотрим код, реализующий оставшиеся способы доступа к иконкам.

Определяем статические свойства и ключи ресурсов



copy to clipboardподсветка кода
  1. public static class SysIcons  
  2. {  
  3.     public static readonly ResourceKey ApplicationKey = new ComponentResourceKey(typeof(SysIcons), «Application»);  
  4.     public static readonly ResourceKey AsteriskKey = new ComponentResourceKey(typeof(SysIcons), «Asterisk»);  
  5.     // И далее в том же духе...  
  6.   
  7.     public static BitmapSource Unknown { get { return LoadIcon(SysIconEnum.Unknown); } }  
  8.     public static BitmapSource Application { get { return LoadIcon(SysIconEnum.Application); } }  
  9.     // И далее в том же духе...  
  10. }  


Теперь иконки можно использовать примерно следующим образом:
copy to clipboardподсветка кода
  1. <Image Source="{x:Static icon:SysIcons.Error}" Width=«24» />  


Создаём расширение XAML-разметки



Создать расширение разметки проще простого, убедитесь в этом сами:
copy to clipboardподсветка кода
  1. [MarkupExtensionReturnType(typeof(ImageSource))]  
  2. public sealed class SysIconExtension : MarkupExtension  
  3. {  
  4.     private SysIconEnum Icon;  
  5.   
  6.     public SysIconExtension(SysIconEnum Icon)  
  7.     {  
  8.         this.Icon = Icon;  
  9.     }  
  10.   
  11.     public override object ProvideValue(IServiceProvider serviceProvider)  
  12.     {  
  13.         return new DynamicResourceExtension(SysIcons.GetKey(Icon)).ProvideValue(serviceProvider);  
  14.     }  
  15. }  

Перед этим определим в статическом классе SysIcons внутренний метод:
copy to clipboardподсветка кода
  1. internal static ResourceKey GetKey(SysIconEnum Icon)  
  2. {  
  3.     switch (Icon)  
  4.     {  
  5.         case SysIconEnum.Application:  
  6.             return ApplicationKey;  
  7.         case SysIconEnum.Asterisk:  
  8.             return AsteriskKey;  
  9.         case SysIconEnum.Error:  
  10.             return ErrorKey;  
  11.         case SysIconEnum.Exclamation:  
  12.             return ExclamationKey;  
  13.         case SysIconEnum.Hand:  
  14.             return HandKey;  
  15.         case SysIconEnum.Information:  
  16.             return InformationKey;  
  17.         case SysIconEnum.Question:  
  18.             return QuestionKey;  
  19.         case SysIconEnum.Shield:  
  20.             return ShieldKey;  
  21.         case SysIconEnum.Unknown:  
  22.             return null;  
  23.         case SysIconEnum.Warning:  
  24.             return WarningKey;  
  25.         case SysIconEnum.WinLogo:  
  26.             return WinLogoKey;  
  27.         default:  
  28.             return null;  
  29.     }  
  30. }  

Проще всего для достижения желаемого было воспользоваться уже существующим расширением DynamicResourceExtension. Динамический ресурс используется для того, чтобы впоследствии можно было «на лету» изменить набор иконок в приложении. Использовать расширение можно так:
copy to clipboardподсветка кода
  1. <Image Source="{icon:SysIcon Error}" />  

Это уже значительно удобнее и короче, чем использование {x:Static ...}. Однако, несмотря на то, что мы ссылаемся на ключи иконок, в автоматически подгружаемом словаре Themes/Generic.xaml они ещё не определены. «Просто так» их там определить не получится, так как использовать {x:Static ...} при определении объектов того же типа в словаре нельзя (необходимо использовать BitmapSource, но именно объект этого типа возвращается статическим свойством). Выход — в создании класса, унаследованного от BitmapSource, в качестве параметра получающего перечисление SysIcons.

Создание наследника BitmapSource


Создавать источник изображения вручную — задача неблагодарная и тяжёлая. Вместо этого можно сделать класс-обёртку над существующим BitmapImage, который будет содержаться в приватном поле нашего класса.

copy to clipboardподсветка кода
  1. public sealed class SysIconImageSource : BitmapSource  
  2. {  
  3.     // Иконка.  
  4.     public SysIconEnum Icon  
  5.     {  
  6.         get { return (SysIconEnum)GetValue(IconProperty); }  
  7.         set { SetValue(IconProperty, value); }  
  8.     }  
  9.   
  10.     // Dependency-свойство.  
  11.     public static readonly DependencyProperty IconProperty =  
  12.         DependencyProperty.Register(«Icon», typeof(SysIconEnum), typeof(SysIconImageSource), new UIPropertyMetadata(SysIconEnum.Unknown));  
  13.   
  14.     // Конструктор объекта.  
  15.     public SysIconImageSource()  
  16.         : base()  
  17.     {  
  18.     }  
  19.   
  20.     // Создание экземпляра (необходимо для потомка Freezable).  
  21.     protected override Freezable CreateInstanceCore()  
  22.     {  
  23.         return new SysIconImageSource();  
  24.     }  
  25.   
  26.     // Поле, содержащее действительное изображение.  
  27.     private BitmapSource _source;  
  28.   
  29.     // Отслеживаеми изменения свойства Icon  
  30.     protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)  
  31.     {  
  32.         base.OnPropertyChanged(e);  
  33.         if (e.Property == IconProperty)  
  34.         {  
  35.             _source = SysIcons.GetSysIcon(Icon);  
  36.             if (_source == null)  
  37.             {  
  38.                 _source = new BitmapImage();  
  39.             }  
  40.             WritePostscript();  
  41.         }  
  42.     }  
  43.   
  44.     // Делаем обёртку для наследуемых методов и свойств...  
  45.     public override void CopyPixels(Array pixels, int stride, int offset)  
  46.     {  
  47.         _source.CopyPixels(pixels, stride, offset);  
  48.     }  
  49.   
  50.     public override double DpiX  
  51.     {  
  52.         get  
  53.         {  
  54.             return _source.DpiX;  
  55.         }  
  56.     }  
  57.   
  58.     // И далее в том же духе...  
  59. }  


Получившийся класс прекрасно работает как источник изображений, и теперь его можно использовать в определении словаря.

Определяем автоматически загружаемый словарь



Создаём словарь Themes/Generic.xaml:
copy to clipboardподсветка кода
  1. <ResourceDictionary  
  2. <SPAN style=«colo
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.