Finding system fonts for use with XAML UWP app #uwpdev

Back in 2011 in my early days of Windows Phone 7 development, I created Alarm Clock app. Over a period of time, I upgraded it to SL8x and made it available on Windows 8x devices. It has seen many iterations and it made sense to rework it for UWP so I don’t have 2 distinct apps any longer – just one that scales across devices.

What was unique about this app ? It has some custom font and alarm sounds.. weird and wonderful – lets just say unusual.

This time around I thought that maybe just maybe I should also allow user to select system fonts!. So how does one go about it ? Well SO #FTW 😛 Filip Skakun replied on http://Loading list of available fonts using c# in winrt mentioned DirectWrite and linked to a git repos

https://github.com/christophwille/winrt-snippets/blob/master/EnumerateFonts/EnumerateFonts/InstalledFont.cs
and
https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/DirectWrite/FontEnumeration/Program.cs

For the sake of showing time, I wasn’t interested in symbol fonts otherwise the source is identical.

private void LoadInstalledFonts()
{
    var factory = new SharpDX.DirectWrite.Factory();
    var fontCollection = factory.GetSystemFontCollection(false);
    var familyCount = fontCollection.FontFamilyCount;
            
    for (int i = 0; i < familyCount; i++)
    {
        var fontFamily = fontCollection.GetFontFamily(i);
                
        var familyNames = fontFamily.FamilyNames;
        int index;

        if (!familyNames.FindLocaleName(CultureInfo.CurrentCulture.Name, out index))
            familyNames.FindLocaleName("en-us", out index);

        string name = familyNames.GetString(index);

        using (var font = fontFamily.GetFont(index))
        {
            if (font.IsSymbolFont)
            {
                continue;
            }
        }

        this.AvailableFonts.Add(new FontFamily(name));
    }
}

The DirectWrite API is exposed by SharpDX and you need to add a reference to SharpDX.Direct2D1 using Nuget Package Manager

That is all that is required. Now the system fonts are available for use however you please. I myself allow user to chose what font they want to use.

Happy Coding

Advertisements

Using Composition Animations in your #uwp app

As we speak a new version of Daily Mail Online is being published to store. I am not going to discuss the content rather stick with code.

I have been testing staged rollout for a week now and that gave me valuable insights. I got errors I could not have reproduced myself. However all this time I was using UWPCommunityToolkit animations – Fade and Offset. Testing out nightly builds post 1.2 release I noticed that the app start time would significantly spiral with initial load taking over 20 seconds. The toolkit animations whilst providing a simple API on the surface are very extensive underneath and talking to folks I decided to create a custom set.

I have in snippet below two border controls

Border b = item.FindDescendantByName("ChannelBackground") as Border;
Border separator = item.FindDescendantByName("ChannelSeparator") as Border;

using toolkit extensions I did

b.Offset(offsetY: -65, duration: animationduration).Start();
separator.Fade(1, animationduration).Start();

so after a big round of search and copy pasting code from around the net, I finally had my own set. To make the change easier, I decided to use different method names so I could test individual bits without affecting it all.

b.AnimateOffsetY(-65, animationduration);
separator.AnimateOpacity(1, animationduration);

One thing that caught me off guard. To not show separator when XAML loads, I set Opacity to 0. With opacity to set, Composition API could not change its opacity any longer. As a fallback I use Storyboard and they seem to work just fine. Took me a while to figure out so now you will notice that in animation extensions, I check UIElement opacity and set it to 1 if it is zero.

Here is the animation extensions.

public static class AnimationExtensions
{
    public static bool UseCompositionAnimations => ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2); // SDK >= 10586

    public static void AnimateOpacity(this UIElement element, float value, double duration = 150)
    {
        if (UseCompositionAnimations)
        {
            if (element.Opacity == 0) // composition animations doesn't have any effect if it is zero
            {
                element.Opacity = 1;
            }
                
            var xamlVisual = ElementCompositionPreview.GetElementVisual(element);

            if (duration <= 0)
            {
                xamlVisual.Opacity = value;
                return;
            }

            var compositor = xamlVisual.Compositor;

            var animation = compositor.CreateScalarKeyFrameAnimation();
                
            animation.Duration = TimeSpan.FromMilliseconds(duration);
            animation.DelayTime = TimeSpan.Zero;
            animation.InsertKeyFrame(1f, value);

            xamlVisual.StartAnimation("Opacity", animation);
        }
        else
        {
            if (duration <= 0)
            {
                element.Opacity = value;
                return;
            }

            AnimateDoubleProperty(element, "Opacity", value, duration);
        }
    }

    public static void AnimateOffsetY(this UIElement element, float value, double duration = 150)
    {
        if (UseCompositionAnimations)
        {
            var xamlVisual = ElementCompositionPreview.GetElementVisual(element);

            var offsetVector = new Vector3(0, value, 0);

            if (duration <= 0)
            {
                xamlVisual.Offset = offsetVector;
                return;
            }

            var compositor = xamlVisual.Compositor;

            var animation = compositor.CreateVector3KeyFrameAnimation();

            animation.Duration = TimeSpan.FromMilliseconds(duration);
            animation.DelayTime = TimeSpan.Zero;
            animation.InsertKeyFrame(1f, offsetVector);

            xamlVisual.StartAnimation("Offset", animation);
        }
        else
        {
            var transform = GetAttachedCompositeTransform(element);

            if (duration <= 0)
            {
                transform.TranslateY = value;
                return;
            }

            string path = GetAnimationPath(transform, element, "TranslateY");

            AnimateDoubleProperty(element, path, value, duration);
        }
    }

    private static string GetAnimationPath(CompositeTransform transform, UIElement element, string property)
    {
        if (element.RenderTransform == transform)
        {
            return $"(UIElement.RenderTransform).(CompositeTransform.{property})";
        }

        var group = element.RenderTransform as TransformGroup;

        if (group == null)
        {
            return string.Empty;
        }

        for (var index = 0; index < group.Children.Count; index++)
        {
            if (group.Children[index] == transform)
            {
                return $"(UIElement.RenderTransform).(TransformGroup.Children)[{index}].(CompositeTransform.{property})";
            }
        }

        return string.Empty;
    }

    private static CompositeTransform GetAttachedCompositeTransform(UIElement element)
    {
        CompositeTransform compositeTransform = null;

        if (element.RenderTransform != null)
        {
            compositeTransform = element.RenderTransform as CompositeTransform;
        }

        if (compositeTransform == null)
        {
            compositeTransform = new CompositeTransform();
            element.RenderTransform = compositeTransform;
        }

        return compositeTransform;
    }

    private static Storyboard AnimateDoubleProperty(
        this DependencyObject target,
        string property,
        double to,
        double duration = 250,
        EasingFunctionBase easingFunction = null)
    {

        var storyboard = new Storyboard();
        var animation = new DoubleAnimation
        {
            To = to,
            Duration = TimeSpan.FromMilliseconds(duration),
            EasingFunction = easingFunction ?? new SineEase(),
            FillBehavior = FillBehavior.HoldEnd,
            EnableDependentAnimation = true
        };

        Storyboard.SetTarget(animation, target);
        Storyboard.SetTargetProperty(animation, property);

        storyboard.Children.Add(animation);
        storyboard.FillBehavior = FillBehavior.HoldEnd;
        storyboard.Begin();

        return storyboard;
    }
}

These are very lightweight animations. If you encounter weird slow downs, maybe consider rolling out your own animation extensions like above. Happy coding

More on #UWPCommunityToolkit CacheBase #uwpdev

In the first version of UWP Community Toolkit, we only had ImageCache which had its origin in Windows App Studio. A few issues were raised to optimise it and one mentioned extensible cache that can be used to create any case.

FileCache, ImageCache, VideoCache, JsonCache.. you name it.. Yesterday I mentioned CacheBase. FileCache and ImageCache that ship with UWP Community Toolkit are implementations of CacheBase by giving it a specific type.

Today I had to implement ability to pull configuration settings from our server. I tried using current prod version of FileCache but my implementation was somewhat wrong there. It would try to create File from Stream and return null and then fail internally (fixed in current dev branch) however I needed something today. Enter ConfigCache.. well JsonCache really

public class ConfigCache : CacheBase<ConfigurationSetting>
{
    JsonSerializer jsonSerializer = new JsonSerializer();

    /// <summary>
    /// Private singleton field.
    /// </summary>
    private static ConfigCache _instance;

    /// <summary>
    /// Gets public singleton property.
    /// </summary>
    public static ConfigCache Instance => _instance ?? (_instance = new ConfigCache() { MaintainContext = false });

    protected override async Task<ConfigurationSetting> InitializeTypeAsync(StorageFile baseFile)
    {
        using (var stream = await baseFile.OpenStreamForReadAsync())
        {
            return InitializeTypeAsync(stream);
        }
    }

    protected override Task<ConfigurationSetting> InitializeTypeAsync(IRandomAccessStream stream)
    {
        var config = InitializeTypeAsync(stream.AsStream());
        return Task.FromResult<ConfigurationSetting>(config);
    }

    private ConfigurationSetting InitializeTypeAsync(Stream stream)
    {
        var reader = new StreamReader(stream);

        using (var jsonReader = new JsonTextReader(reader))
        {
            return jsonSerializer.Deserialize<ConfigurationSetting>(jsonReader);
        }
    }
}

In this case I am using a specific type to deserialise json to. How do I use it ?

ConfigCache.Instance.CacheDuration = TimeSpan.FromDays(1);

this.ConfigurationSettings = await ConfigCache.Instance.GetFromCacheAsync(new Uri(urlPath));

This would ensure that if configuration is older than a day, it will be downloaded again. Either way the caller will get deserialised data.

VideoCache #uwp #uwpdev

Earlier today (10 mins ago), we were discussing looping animated previews (rolling video frames) for articles and that maybe we should create a cache to prevent it being downloaded over and over again.

Back in UWP Community Toolkit, we created a CacheBase – a generic base implementation of Cache. This can be used to create just about any type of data cache.

This CacheBase was then used to implement ImageCache and finally FileCache just to show it can be done with other types too. So this is what I did.

public class VideoCache : CacheBase<MediaSource>
{
    /// <summary>
    /// Private singleton field.
    /// </summary>
    private static VideoCache _instance;

    /// <summary>
    /// Gets public singleton property.
    /// </summary>
    public static VideoCache Instance => _instance ?? (_instance = new VideoCache() { MaintainContext = true });

    protected override Task<MediaSource> InitializeTypeAsync(StorageFile baseFile)
    {
        return Task.Run(() => MediaSource.CreateFromStorageFile(baseFile));
    }

    protected override Task<MediaSource> InitializeTypeAsync(IRandomAccessStream stream)
    {
        return Task.Run(() => MediaSource.CreateFromStream(stream, "video/mp4"));
    }
}

Previously in my video player (see my previous blog post), I created a MediaSource from Uri.. now this to use the Cache

//MediaSource mediaSource = MediaSource.CreateFromUri(this.Video.Uri);
MediaSource mediaSource = await VideoCache.Instance.GetFromCacheAsync(this.Video.Uri);

Now the animated previews are not only cached and reused but they are also available offline.

To ensure that you do not build a large set, always make sure you set CacheDuration and remove expired items.

// somewhere in your App.xaml.cs
VideoCache.Instance.CacheDuration = TimeSpan.FromDays(1);

elsewhere in your cleanup code

await VideoCache.Instance.RemoveExpiredAsync();

Took me about 2 mins to roll out and get it running.