Skip to content

[API Proposal]: Win11 Theming - Icon element support #8647

@pchaurasia14

Description

@pchaurasia14

Part of effort - #8655

Background and motivation

Icons are an important part of Win11 design system. With the release of Windows 11, the Segoe Fluent Icons font is the recommended symbol icon font. In this proposal, we have the API changes for supporting symbol icon font with ease in WPF applications.

User Scenarios

  1. [P1] Application developers should be able to use Windows 11 system icon font to render icons in the application.
  2. [P1] Application developers can use custom font icons in there WPF applications.
  3. [P1] Developers have the freedom to modify the size, style, weight of the font icons.
  4. [P2] Application developers should be able to use markup extensions to set icon
  5. [P3] Application developers can use icon names to render commonly used icons.
  6. [P3] Application developers can use other objects like Path, Image, Bitmap, etc as icon. Provide extensibility for the same.

Code Example

    <FontIcon FontSize="16" Glyph="&#x03A3;" 
                FontFamily="Segoe Fluent Icons" 
                FontWeight="Bold" />

    <IconSourceElement>
        <IconSourceElement.IconSource>
            <FontIconSource Glyph="&#x03A3;" FontFamily="Segoe Fluent Icons">
        </IconSourceElement.IconSource>
    </IconSourceElement>
    var fontIcon = new FontIcon();
    fontIcon.FontFamily = new FontFamily("Segoe Fluent Icons");
    fontIcon.Glyph = "\xE790";

API Proposals Iteration 1


IconElement

  • Provides the base class support for adding icon related feature to controls.
+    [System.ComponentModel.TypeConverterAttribute(typeof(System.Windows.Controls.IconElementConverter))]
+    public abstract class IconElement : System.Windows.FrameworkElement
+    {
+        protected abstract System.Windows.UIElement InitializeChildren();
+        protected IconElement() { }

+        public static readonly System.Windows.DependencyProperty ForegroundProperty;
+        [System.ComponentModel.BindableAttribute(true)]
+        [System.ComponentModel.CategoryAttribute("Appearance")]
+        public System.Windows.Media.Brush Foreground { get; set; }

+        protected override int VisualChildrenCount { get; }

+        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize);
+        protected override System.Windows.Media.Visual GetVisualChild(int index);
+        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize);
+    }

FontIcon

  • Concrete class that implements IconElement. Represents an icon that uses a glyph from the specified font.
  • Defines following dependency properties -
    • FontFamily : Gets or sets the font used to display the icon glyph.
    • FontSize : Gets or sets the size of the icon glyph.
    • FontStyle : Gets or sets the font style for the icon glyph.
    • FontWeight : Gets or sets the thickness of the icon glyph
    • Glyph : Gets or sets the character code that identifies the icon glyph.
+    public class FontIcon : System.Windows.Controls.IconElement
+    {
+        public FontIcon() { }

+        public static readonly System.Windows.DependencyProperty FontFamilyProperty;
+
+        [System.ComponentModel.BindableAttribute(true)]
+        [System.ComponentModel.CategoryAttribute("Appearance")]
+        [System.Windows.LocalizabilityAttribute(System.Windows.LocalizationCategory.Font)]
+        public System.Windows.Media.FontFamily FontFamily { get; set; }
+
+
+        public static readonly System.Windows.DependencyProperty FontSizeProperty;
+
+        [System.ComponentModel.BindableAttribute(true)]
+        [System.ComponentModel.CategoryAttribute("Appearance")]
+        [System.ComponentModel.TypeConverterAttribute(typeof(System.Windows.FontSizeConverter))]
+        [System.Windows.LocalizabilityAttribute(System.Windows.LocalizationCategory.None)]
+        public double FontSize { get; set; }

+        public static readonly System.Windows.DependencyProperty FontStyleProperty;
+        [System.ComponentModel.BindableAttribute(true)]
+        [System.ComponentModel.CategoryAttribute("Appearance")]
+        public System.Windows.FontStyle FontStyle { get; set; }

+        public static readonly System.Windows.DependencyProperty FontWeightProperty;
+        [System.ComponentModel.BindableAttribute(true)]
+        [System.ComponentModel.CategoryAttribute("Appearance")]
+        public System.Windows.FontWeight FontWeight { get; set; }

+        public static readonly System.Windows.DependencyProperty GlyphProperty;
+        public string Glyph { get; set; }

+        protected override System.Windows.UIElement InitializeChildren();
+    }

IconSourceElement

  • Represents an icon element that uses an IconSource object as its content.
+    [System.Windows.Markup.ContentPropertyAttribute("IconSource")]
+    public class IconSourceElement : System.Windows.Controls.IconElement
+    {
+        public IconSourceElement() { }

+        public static readonly System.Windows.DependencyProperty IconSourceProperty;
+        public System.Windows.Controls.IconSource IconSource { get; set; }

+        protected override System.Windows.UIElement InitializeChildren();
+    }

ImageIcon

  • Represents an icon that uses an System.Windows.Controls.Image as its content.
+    [System.Windows.Markup.ContentPropertyAttribute("IconSource")]
+    public class ImageIcon : System.Windows.Controls.IconElement
+    {
+        public ImageIcon() { }

+        public static readonly System.Windows.DependencyProperty SourceProperty;
+        public System.Windows.Controls.ImageSource Source { get; set; }

+        protected override System.Windows.UIElement InitializeChildren();
+    }

IconSource

  • Represents a base class for an icon source. We can use this in combination with IconSourceElement to add icons to WPF applications. This is similar to IconElement, however since this is not a FrameworkElement it can be shared.
+    public abstract class IconSource : System.Windows.DependencyObject
+    {
+        protected IconSource() { }

+        public static readonly System.Windows.DependencyProperty ForegroundProperty;
+        public System.Windows.Media.Brush Foreground { get; set; }

+        public abstract System.Windows.Controls.IconElement CreateIconElement();
+    }

FontIconSource

  • Represents an icon source that uses a glyph from the specified font.
+    public class FontIconSource : IconSource
+    {
+        public FontIconSource() { }

+        public static readonly System.Windows.DependencyProperty FontFamilyProperty;
+        public System.Windows.Media.FontFamily FontFamily { get; set; }

+        public static readonly System.Windows.DependencyProperty FontSizeProperty;
+        public double FontSize { get; set; }

+        public static readonly System.Windows.DependencyProperty FontStyleProperty;
+        public System.Windows.FontStyle FontStyle { get; set; }

+        public static readonly System.Windows.DependencyProperty FontWeightProperty;
+        public System.Windows.FontWeight FontWeight { get; set; }

+        public static readonly System.Windows.DependencyProperty GlyphProperty;
+        public string Glyph { get; set; }

+        public override System.Windows.Controls.IconElement CreateIconElement();
+    }

IconSourceElementConverter

  • Converting an IconSourceElement to IconElement
+    public partial class IconSourceElementConverter : System.Windows.Data.IValueConverter
+    {
+        public IconSourceElementConverter() { }
+        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture);
+        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture);
+    }

ImageIconExtension

  • Custom markup extension that provides ImageIcon

Usage -

<.... Icon="{ImageIcon 'pack://application:,,,/Assets/icon.png'}" ... />
+   [ContentProperty(nameof(Source))]
+   [MarkupExtensionReturnType(typeof(ImageIcon))]
+   public class ImageIconExtension : MarkupExtension
+   {
+       public ImageIconExtension(ImageSource? source);
+   
+       [ConstructorArgument("source")]
+       public ImageSource? Source { get; set; }
+   
+       public double Width { get; set; } = 16D;
+   
+       public double Height { get; set; } = 16D;
+   
+       public override object ProvideValue(IServiceProvider serviceProvider);
+   }

FontIconExtension

  • Custom markup extension that provides FontIcon

Usage -

<.... Icon="{FontIcon Glyph="'&amp;#x1F308;'" FontFamily="Cascadia" FontSize=10 }" ... />
+   [ContentProperty(nameof(Glyph))]
+   [MarkupExtensionReturnType(typeof(FontIcon))]
+   public class FontIconExtension : MarkupExtension
+   {
+       public FontIconExtension(string glyph);
+   
+       public FontIconExtension(string glyph, FontFamily fontFamily): this(glyph);
+   
+       [ConstructorArgument("glyph")]
+       public string Glyph { get; set; }
+   
+       [ConstructorArgument("fontFamily")]
+       public FontFamily FontFamily { get; set; }
+   
+       public double FontSize { get; set; }
+   
+       public override object ProvideValue(IServiceProvider serviceProvider);
+   }

Alternative Designs

In an another approach for the above, we can eliminate derived classes of IconElement, have an IconSource property in IconElement, and we keep IconSource and derived classes ( FontIconSource, ImageIconSource, ...) alongwith corresponding icon source extensions ( FontIconSourceExtension, ImageIconSourceExtension, ... ).

If we follow this approach, the usage of APIs will be as follows:

    <IconElement>
        <IconElement.IconSource>
            <FontIconSource Glyph="&#x03A3;" FontFamily="Segoe Fluent Icons">
        </IconElement.IconSource>
    </IconElement>

    <IconElement IconSource="{FontIconSource '&#x03A3;' }">
    var iconElement = new IconElement();
    var fontIconSource = new FontIconSource();
    fontIconSource.FontFamily = new FontFamily("Segoe Fluent Icons");
    fontIconSource.Glyph = "\xE790";

IconElement

As mentioned above, here is the IconElement API for the above approach:

    public partial class IconElement : System.Windows.FrameworkElement
    {
        public IconElement() { }
        
        public static readonly System.Windows.DependencyProperty IconSourceProperty;
        public System.Windows.Controls.IconSource IconSource { get { throw null; } set { } }
        
        protected virtual System.Windows.UIElement InitializeChildren() { throw null; }
        
        protected override int VisualChildrenCount { get { throw null; } }
        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) { throw null; }
        protected override System.Windows.Media.Visual GetVisualChild(int index) { throw null; }
        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) { throw null; }
    }

IconSource, FontIconSource, FontIconSourceExtension, ...

    public abstract partial class IconSource : System.Windows.DependencyObject
    {
        protected IconSource() { }
        
        public static readonly System.Windows.DependencyProperty ForegroundProperty;
        public System.Windows.Media.Brush Foreground { get { throw null; } set { } }
        
        public abstract System.Windows.UIElement CreateIconContent();
    }

    public partial class FontIconSource : System.Windows.Controls.IconSource
    {
        public FontIconSource() { }
        
        public static readonly System.Windows.DependencyProperty FontFamilyProperty;
        public static readonly System.Windows.DependencyProperty FontSizeProperty;
        public static readonly System.Windows.DependencyProperty FontStyleProperty;
        public static readonly System.Windows.DependencyProperty FontWeightProperty;
        public static readonly System.Windows.DependencyProperty GlyphProperty;
        
        public System.Windows.Media.FontFamily FontFamily { get { throw null; } set { } }
        public double FontSize { get { throw null; } set { } }
        public System.Windows.FontStyle FontStyle { get { throw null; } set { } }
        public System.Windows.FontWeight FontWeight { get { throw null; } set { } }
        public string Glyph { get { throw null; } set { } }
        
        public override System.Windows.UIElement CreateIconContent() { throw null; }
    }

    [System.Windows.Markup.ContentPropertyAttribute("Glyph")]
    [System.Windows.Markup.MarkupExtensionReturnTypeAttribute(typeof(System.Windows.Controls.FontIconSource))]
    public partial class FontIconSourceExtension : System.Windows.Markup.MarkupExtension
    {
        public FontIconSourceExtension(string glyph) { }
        public FontIconSourceExtension(string glyph, System.Windows.Media.FontFamily fontFamily) { }
        
        [System.Windows.Markup.ConstructorArgumentAttribute("fontFamily")]
        public System.Windows.Media.FontFamily FontFamily { get { throw null; } set { } }
        
        public double FontSize { get { throw null; } set { } }
        
        [System.Windows.Markup.ConstructorArgumentAttribute("glyph")]
        public string Glyph { get { throw null; } set { } }
        
        public override object ProvideValue(System.IServiceProvider serviceProvider) { throw null; }
    }

Risks

Unknown

cc: @dotnet/dotnet-wpf-triage @pomianowski

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    ☑ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions