Be moar reactive with #uwpdev ?

This is the 4th and hopefully not the last post ever about Reactive Extensions with #uwpdev. After my 1st post, Jamie Mutton mentioned that writing async task code inside a subscription can lead to side effects and he posted a gist on various options.

I decided to try and reduce some of the async task code from subscription and into another observable following the first option Jamie suggested. Here’s what I had before.

The subscription in the constructor of the ViewModel.

this.ChannelDataService.ActivateChannelSubject
    .ObserveOn(RxApp.TaskpoolScheduler)
    .SubscribeOn(RxApp.TaskpoolScheduler)
    .Subscribe(async tuple =>
{
    if (tuple != null)
    {
        await this.ProcessChannelData();
    }
});

this.ChannelDataService.ChannelDataSubject
    .ObserveOn(RxApp.TaskpoolScheduler)
    .SubscribeOn(RxApp.TaskpoolScheduler)
    .Subscribe(async tuple =>
{
    if (tuple != null)
    {
        await this.ProcessChannelData();
    }
});

The View Model methods

private async Task ProcessChannelData()
{
    try
    {
        await semaphore.WaitAsync();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.IsBusy = true);

        var channelData = this.ChannelDataService.GetChannelData();
        await this.RenderChannelData(channelData);
    }
    finally
    {
        semaphore.Release();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.IsBusy = false);
    }
}

private async Task RenderChannelData(ChannelData channelData)
{
    if (channelData == null)
    {
        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.ResetData());

        return;
    }

    var configSettings = this.ChannelDataService.ConfigurationSettings;

    var itemData = await channelData?.ProcessItems(ChannelService.ChannelDictionary, ChannelService.ChannelList[0], this.AppSettings.Country);

    if (itemData == null)
    {
        return;
    }

    var puffData = channelData.ProcessPuffs(ChannelService.ChannelDictionary, ChannelService.ChannelList[0]);

    System.Diagnostics.Debug.WriteLine($"Item count is {itemData.Item1.Count}, Group count is {itemData.Item2.Count}");

    await DispatcherHelper.ExecuteOnUIThreadAsync(() =>
    {
        try
        {
            ViewHelper.SetStatusBar(this.Channel.StatusColor, Colors.White, this.AppSettings.ElementTheme);

            if (this.Items.Count > 0)
            {
                this.Items.Clear();
            }

            this.Items.AddRange(itemData.Item1);

            if (this.Content.Count > 0)
            {
                this.Content.Clear();
            }

            this.Content.AddRange(itemData.Item2);
        }
        catch { }

        try
        {
            if (PuffContent.Count > 0)
            {
                this.PuffContent.Clear();
            }

            this.PuffContent.AddRange(puffData);
        }
        catch { }
    });
}

I tried to split the code into two parts – Fetch and prepare data in one and render in another.

private async Task<ChannelData> ProcessChannelDataAsync()
{
    ChannelData channelData = null;
    try
    {
        await semaphore.WaitAsync();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() =>
        {
            this.IsBusy = true;
        });

        await Task.Run(() =>
        {
            channelData = this.ChannelDataService.GetChannelData();

            if (channelData != null)
            {
                channelData.ProcessChannelData(ChannelService.ChannelDictionary, ChannelService.ChannelList[0], this.AppSettings.Country);
            }
        });
    }
    catch (JsonReaderException jsonEx)
    {
        Exception handledException = new Exception("Handled data parse exception", jsonEx);
        HockeyClient.Current.TrackException(handledException);
    }
    catch (SqliteException ex)
    {
        Exception handledEx = new Exception("Handled SQLite error getting channel data", ex);
        HockeyClient.Current.TrackException(handledEx);

        DialogService ds = new DialogService();
        await ds.ShowMessage("Application encountered serious error. Please reinstall the application.", SystemInformation.ApplicationName);
    }
    finally
    {
        semaphore.Release();
    }

    return channelData;
}

private async Task RenderChannelDataAsync(ChannelData processedData)
{
    try
    {
        await semaphore.WaitAsync();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() =>
        {
            ViewHelper.SetStatusBar(this.Channel.StatusColor, Colors.White, this.AppSettings.ElementTheme);

            if (processedData == null)
            {
                this.ResetData();

                return;
            }

            if (this.Items.Count > 0)
            {
                this.Items.Clear();
            }

            if (processedData != null)
            {
                this.Items.AddRange(processedData.ProcessedItems);
            }

            if (this.Content.Count > 0)
            {
                this.Content.Clear();
            }

            if (processedData != null)
            {
                this.Content.AddRange(processedData.GroupedItems);
            }

            if (PuffContent.Count > 0)
            {
                this.PuffContent.Clear();
            }

            if (processedData != null)
            {
                this.PuffContent.AddRange(processedData.PuffGroupedItems);
            }

            this.IsBusy = false;
        });
    }
    catch { }
    finally
    {
        semaphore.Release();
    }
}

Now I could chain the original observation with a new observable(s) created out of async method(s) as suggested by Jamie Mutton in his gist.

The extension method to convert async method to an observable is simply ToObservable().

this.ChannelDataService.ActivateChannelSubject
    .Where(t => t != null)
    .Select(_ => Observable.Defer(() => ProcessChannelDataAsync().ToObservable()))
    .Concat()
    .Select(processedData => Observable.Defer(() => this.RenderChannelDataAsync(processedData).ToObservable()))
    .Concat()
    .Subscribe(_ =>
    {
        System.Diagnostics.Debug.WriteLine($"Active channel changed");
    });

this.ChannelDataService.ChannelDataSubject
    .Where(t => t != null)
    .Select(_ => Observable.Defer(() => ProcessChannelDataAsync().ToObservable()))
    .Concat()
    .Select(processedData => Observable.Defer(() => this.RenderChannelDataAsync(processedData).ToObservable()))
    .Concat()
    .Subscribe(processedData =>
    {
        System.Diagnostics.Debug.WriteLine($"Channel data updated");
    });

Performance isn’t much significantly different from earlier but the code is now more reactive than before. The subscription as I was told is as light as can be and there is little room for side-effects

Happy coding.

Advertisement

Reactive Extensions with #uwpdev Part 3

The way I understood Rx, it was designed to do away with how we (developers) handle events.

A ListView control provides a few ways of handling how developers deal with user interaction. You can have ItemClick and disable Item Selection. You could have Item Selection with ability to select more than one Item.
My personal favourite is to allow ItemClick and to bind a Command property in ListViewExtensions in UWP Toolkit.

There are times when you really need to handle the event in code behind – I know purists would have a day here but say you had a video previews running in a list view. You don’t want all of them running all the time – you want to ensure that only those rendered on screen are running the video.
How would one do that ?

The usual route would be to find the ScrollViewer in the ListView control and hook into the ViewChanged event.
Every time ViewChanged event is raised, OnScrollViewerViewChanged method will be called.

By every time I mean every time.. could be maybe times every second. You then start have to add hacks like lastChecked time and if Time Elapsed is greater than 100ms etc etc.

private void WireUpListViewScroll(ListView listView)
{
    if (listView == null)
    {
        return;
    }

    if (scrollViewer != null)
    {
        scrollViewer.ViewChanged -= OnScrollViewerViewChanged;
    }

    var scrollViewer = listView.FindDescendant<ScrollViewer>();

    if (scrollViewer == null)
    {
        return;
    }

    scrollViewer.ViewChanged += OnScrollViewerViewChanged;
    
    ProcessChannelUserControls(listView, true);
}

private void OnScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    this.ProcessChannelUserControls(listView);
}

So lets look up what Reactive extensions has for us. For starters, Observable.FromEventPattern allows you to create an observable from event and to subscribe to it. No need to unsubscribe from event handers etc.. just dispose the subscription.

Notice I also use Throttle with a TimeSpan.. I don’t want too many hits – once every 0.35 seconds seemed sufficiently responsive for my use and what’s what I used. the scrollViewerSub I use is a SerialDisposable – every time I add a new subscription, it disposes the old one.

private void WireUpListViewScroll(ListView listView)
{
    if (listView == null)
    {
        return;
    }

    var scrollViewer = listView.FindDescendant<ScrollViewer>();

    if (scrollViewer == null)
    {
        return;
    }

    this.scrollViewerSub.Disposable = Observable.FromEventPattern(scrollViewer, "ViewChanged")
        .Throttle(TimeSpan.FromSeconds(0.35))
        .ObserveOn(CoreDispatcherScheduler.Current)
        .SubscribeOn(TaskPoolScheduler.Default)
        .Subscribe(x =>
        {
            this.ProcessChannelUserControls(listView);
        });

    ProcessChannelUserControls(listView, true);
}

Now that the ViewChanged logic is in place, let’s look at the rest of the code for toggling video previews.

private async void ProcessChannelUserControls(ListView listview, bool initial = false)
{
    try
    {
        var isp = listview.ItemsPanelRoot as ItemsStackPanel;

        if (isp == null)
        {
            return;
        }

        await ToggleAnimatedPreviews(listview, isp, initial);
    }
    catch { }
}

private async Task ToggleAnimatedPreviews(ListView listView, ItemsStackPanel isp, bool initial)
{
    var firstGroupPos = isp.FirstVisibleIndex;
    var lastGroupPos = isp.LastVisibleIndex;

    if (firstGroupPos == -1 && lastGroupPos == -1)
    {
        firstGroupPos = 0;
        lastGroupPos = 0;
    }

    if (initial)
    {
        DependencyObject obj = null;
        while (true)
        {
            obj = listView.ContainerFromIndex(0);

            if (obj != null)
            {
                break;
            }

            await Task.Delay(100);
        }
    }

    for (int i = 0; i < listView.Items.Count; i++)
    {
        var container = listView.ContainerFromIndex(i);

        var vp = container?.FindDescendant<AnimatedPreviewPlayerUC>();

        if (i >= firstGroupPos && i <= lastGroupPos)
        {
            vp?.Play();
        }
        else
        {
            vp?.Pause();
        }
    }
}

These are some of the places I have used Reactive Extensions whilst doing #uwpdev with MVVM. I am still learning.

Reactive Extensions with #uwpdev Part 2

In my post yesterday I talked about adding a few reactive bits inside MVVM using ReactiveUI.

Jamie Mutton mentioned that writing async task code inside a subscription can lead to side effects and he posted a gist on various options. I haven’t tried those yet – I will do so soon. I have mentioned something about not being a purist and I found it easier to inject Rx elements into MVVM whilst following known (to myself) programming style.
Being a slow learner, I prefer to inject things slowly. Add reactive bits at your own pace. You can go the whole 9 yards or go a step at a time.

Today let’s see how to add Reactive Extensions to Service layer. MVVM should really be MVVMS but its a mouthful and not a palindrome.

The Daily Mail Online app has many channels that group content as desired by the editors. Within the app, there’s a Channel list and the content is displayed within FlipView.

Channel list is bound to a RelayCommand (MVVMLight) – setting the correct ChannelIndex. The FlipView is bound to a list of ChannelViewModels with ChannelIndex as the SelectedIndex. When the channel changes, the Data Service is notified and new data is requested.

The sequence is as follows

  1. When Channel Index changes, it pushes a new Tuple through the Active Channel Subject.
  2. When Active Channel Subject publishes a new item:
    • the subscriber in the service, if data is out of data, it downloads new data set and saves it to database, publishing the tuple through to Channel Data Subject
    • the subscriber in the view model, pulls and renders data from database
  3. When Channel Data Subject publishes a new item, the view model pulls it from database and renders it
public int ChannelIndex
{
    get
    {
        return this.channelIndex;
    }

    set
    {
        try
        {
            this.RaiseAndSetIfChanged(ref this.channelIndex, value);
            this.RaisePropertyChanged(nameof(this.Channel));
            this.RaisePropertyChanged(nameof(this.ChannelViewModel));

        }
        catch { }

        var channel = this.Channel;

        SubChannel selectedSub = null;

        if (channel.HasSubChannels)
        {
            selectedSub = channel.GetDefaultSubChannel();
        }

        this.ChannelDataService.SetActiveSource(new Tuple(channel, selectedSub));
    }
}

The Channel Data Service is a monstrosity in itself but that’s another issue. What I have in the service are Reactive Subject instances to publish – in this instance a Tuple for both ActiveChannel and when a new data set is available.

Subjects are considered to be educational material – however in absence of any alternative, I decided to start with those.

public BehaviorSubject ActivateChannelSubject { get; private set; }

public BehaviorSubject ChannelDataSubject { get; private set; }

As you saw above, the ChannelIndex calls a method SetActiveSource. This merely pushes item to the Subject.

public void SetActiveSource(Tuple tuple)
{
    this.SelectedChannelTuple = tuple;

    this.ActivateChannelSubject.OnNext(tuple);
}

Within the service itself, I have a subscription to this subject. Other subscriber includes the MainViewModel for the app

this.ActivateChannelSubject.Subscribe(async tuple =>
{
    try
    {
        // checks to see if existing data is up to date.
        // If it isn't, it pulls necessary content
        await this.ProcessChannel(tuple, true);
    }
    catch { }
});
public async Task ProcessChannel(
    Tuple channelTuple,
    bool notifySubscribers,
    bool forceRefresh = false)
{
    if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
    {
        return;
    }

    try
    {
        // check if data is up to date

        // download content as json

        // inject new data into the local db

        if (notifySubscribers)
        {
            this.ChannelDataSubject.OnNext(channelTuple);
        }
    }
}

I remember the warning Jamie gave about the side effects when using async task in subscription. I promise to take a look at the alternative but this is what it is right now..
The try catch around ProcessChannel are just to make sure any exceptions do not take the app down as async lambda is async void and it can burn the house down if not very careful.

Lets took at the subscriptions in the view model’s constructor

this.ChannelDataService.ActivateChannelSubject
    .ObserveOn(RxApp.TaskpoolScheduler)
    .SubscribeOn(RxApp.TaskpoolScheduler)
    .Subscribe(async tuple =>
{
    if (tuple != null)
    {
        await this.ProcessChannelData();
    }
});

this.ChannelDataService.ChannelDataSubject
    .ObserveOn(RxApp.TaskpoolScheduler)
    .SubscribeOn(RxApp.TaskpoolScheduler)
    .Subscribe(async tuple =>
{
    if (tuple != null)
    {
        await this.ProcessChannelData();
    }
});

The View Model itself pulls the data, groups it and renders it on UI thread

private async Task ProcessChannelData()
{
    try
    {
        await semaphore.WaitAsync();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.IsBusy = true);

        var channelData = this.ChannelDataService.GetChannelData();
        await this.RenderChannelData(channelData);
    }
    finally
    {
        semaphore.Release();

        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.IsBusy = false);
    }
}

private async Task RenderChannelData(ChannelData channelData)
{
    if (channelData == null)
    {
        await DispatcherHelper.ExecuteOnUIThreadAsync(() => this.ResetData());

        return;
    }

    var configSettings = this.ChannelDataService.ConfigurationSettings;

    var itemData = await channelData?.ProcessItems(ChannelService.ChannelDictionary, ChannelService.ChannelList[0], this.AppSettings.Country);

    if (itemData == null)
    {
        return;
    }

    var puffData = channelData.ProcessPuffs(ChannelService.ChannelDictionary, ChannelService.ChannelList[0]);

    System.Diagnostics.Debug.WriteLine($"Item count is {itemData.Item1.Count}, Group count is {itemData.Item2.Count}");

    await DispatcherHelper.ExecuteOnUIThreadAsync(() =>
    {
        try
        {
            ViewHelper.SetStatusBar(this.Channel.StatusColor, Colors.White, this.AppSettings.ElementTheme);

            if (this.Items.Count > 0)
            {
                this.Items.Clear();
            }

            this.Items.AddRange(itemData.Item1);

            if (this.Content.Count > 0)
            {
                this.Content.Clear();
            }

            this.Content.AddRange(itemData.Item2);
        }
        catch { }

        try
        {
            if (PuffContent.Count > 0)
            {
                this.PuffContent.Clear();
            }

            this.PuffContent.AddRange(puffData);
        }
        catch { }
    });
}

This I believe is like 11/2 steps at max.. There a long way to go.. much to refine but things work. I force use of TaskPool unless I need a UI.. in that case, queue it up on the Dispatcher.

I have done it the non reactive way before and whilst it all works, it feels better with reactive model. Things split into independently observables

Reactive Extensions with #uwpdev

Traditionally modern Windows application development (something done in the last 10 years) would involve using XAML. XAML is a great way to define a UI – it addition to it being declarative, it also supports Binding and Commanding.
MVVM – Model View View-Model has become the defacto pattern when developing XAML based applications… even a slow learner like myself eventually graduated to MVVM.

Anything that uses Binding needs to implement INotifyPropertyChanged interface by means on invoking PropertyChanged event to notify the UI of change.

MvvmLight toolkit which provides a large collection of helpful plumbing (RelayCommand, SimpleIoC, NavigationService and more) also provides an object called ObservableObject. This implements the INotifyPropertyChanged and devs just have to inherit from it and introduce their own properties ready for binding.
ReactiveUI a Reactive Extensions MVVM toolkit provides a similar class called ReactiveObject.

namespace ReactiveUI
{
    //
    // Summary:
    //     ReactiveObject is the base object for ViewModel classes, and it implements INotifyPropertyChanged.
    //     In addition, ReactiveObject provides Changing and Changed Observables to monitor
    //     object changes.
    [DataContract]
    public class ReactiveObject : IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject, INotifyPropertyChanged, INotifyPropertyChanging, IEnableLogger
    {
        protected ReactiveObject();

        //
        // Summary:
        //     Represents an Observable that fires *before* a property is about to be changed.
        [IgnoreDataMember]
        public IObservable Changing { get; }
        //
        // Summary:
        //     Represents an Observable that fires *after* a property has changed.
        [IgnoreDataMember]
        public IObservable Changed { get; }
        //
        [IgnoreDataMember]
        public IObservable ThrownExceptions { get; }

        public event PropertyChangingEventHandler PropertyChanging;
        public event PropertyChangedEventHandler PropertyChanged;

        //
        public bool AreChangeNotificationsEnabled();
        public IDisposable DelayChangeNotifications();
        //
        public IDisposable SuppressChangeNotifications();
    }
}

When creating observable objects, even ViewModel I tend to derive from ReactiveObject. Any derived class has access to tons of ReactiveExtensions.

For example, I mentioned that my work app Daily Mail Online is a SPA UWP app. I use a property called ViewMode to identify what View is loaded / shown to the user and what isn’t.

public ViewModes ViewMode
{
    get
    {
        return this.viewMode;
    }

    private set
    {
        this.RaiseAndSetIfChanged(ref this.viewMode, value);
    }
}

The method RaiseAndSetIfChanged is another extension method available to a reactive object. This raises PropertyChanged event if value has changed and UI needs to be informed. This would be great if ViewMode was bound to XAML but I wanted to observe any changes from different places. What I created was an observable using another extension method

public IObservable ViewModeObservable { get; private set; }
this.ViewModeObservable = this.WhenAnyValue(x => x.ViewMode)
    .Where(x => x != ViewModes.NotSet)
    .ObserveOn(CoreDispatcherScheduler.Current)
    .SubscribeOn(TaskPoolScheduler.Default);

I used an extension method called WhenAnyValue. It allows one to convert any property the raises PropertyChanged event as Observable.

Notice that I have set ObserveOn and SubscribeOn methods – those are not necessary here rather a subscriber can choose to set those. I set those here because I know this needs to be run on UI thread and any subscribers would need to do the same.
Now lets see how we can observe changes raised by this observable.

I hook into the ViewModel’s observable property in the View’s code behind. I am not an MVVM purist and I am happy to get into code behind if needed

this.MainViewModel.ViewModeObservable.Subscribe(async viewMode =>
{
    await _semaphoreViewMode.WaitAsync();

    try
    {
        switch (viewMode)
        {
            case ViewModes.Article:

                // do something
                break;

            case ViewModes.RelatedArticle:

                // do something
                break;

            case ViewModes.Channel:

                // do something
                break;

            case ViewModes.ReadLater:

                // do something
                break;

            case ViewModes.Comments:

                // do something
                break;

            case ViewModes.Search:

                // do something
                break;

            case ViewModes.Topic:

                // do something
                break;

            case ViewModes.Profile:

                // do something
                break;
        }
    }
    finally
    {
        _semaphoreViewMode.Release();
    }
});

The Subscribe extension method returns an instance of IDisposable. One can use that or use SerialDisposable to auto dispose previous subscription when a new subscription happens.

One can Subscribe to an Observable as many places as one needs.

Note: Its very likely that I am doing Reactive Extensions all wrong. Feel free to suggest better ways of using Rx

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.

Media playback controls and strange behaviour of transport controls #uwp

Windows 10 WinRT API introduced ability to query whether a certain class, method, property etc were supported during run-time or not. This would allow applications to conditionally do things rather than crashing out due to unsupported API.

Since the initial release, many APIs have been introduced, Composition layer has seen many enhancements. Controls like MediaElementPlayer which return the load on UI thread by directly targeting GPU have been introduced. Prior to that developers used MediaElement control that was introduced in Windows 8 / Windows Phone 8.1 and was included in UWP API 10.0.10240.0. To take advantage of better performing control on updated devices, we need to use newer control.

private bool UseMediaPlayerElement => ApiInformation.IsTypePresent("Windows.UI.Xaml.Controls.MediaPlayerElement");

At this point we know whether we can use MediaElementPlayer or we need to fallback to MediaElement. Following the guidelines provided by Microsoft to an extent, I created a user control that dynamically injects the right control

public sealed partial class VideoPlayerUC : UserControl
{
    FrameworkElement mediaControl = null;

    private bool UseMediaPlayerElement => 
        ApiInformation.IsTypePresent("Windows.UI.Xaml.Controls.MediaPlayerElement");

    public VideoPlayerUC()
    {
        this.InitializeComponent();
            
        if (this.UseMediaPlayerElement)
        {
            mediaControl = new MediaPlayerElement();
        }
        else
        {
            mediaControl = new MediaElement();
        }

        this.Root.Children.Add(mediaControl);
    }

    public Video Video
    {
        get { return (Video)GetValue(VideoProperty); }
        set { SetValue(VideoProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Video.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty VideoProperty =
        DependencyProperty.Register("Video", 
        typeof(Video), 
        typeof(VideoPlayerUC), 
        new PropertyMetadata(null, OnVideoChanged));

    private static void OnVideoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as VideoPlayerUC;
        control?.HandleVideoChange();
    }

    private void HandleVideoChange()
    {
        if (this.Video == null)
        {
            return;
        }

        MediaSource mediaSource = MediaSource.CreateFromUri(this.Video.Uri);

        MediaPlaybackItem playbackItem = new MediaPlaybackItem(mediaSource);

        if (this.UseMediaPlayerElement)
        {
            var mpe = mediaControl as MediaPlayerElement;

            mpe.Stretch = Stretch.Uniform;
            mpe.AreTransportControlsEnabled = this.Video.ShowControls;
            mpe.Source = playbackItem;
            mpe.PosterSource = new BitmapImage() { UriSource = this.Video.Images.Still.Uri };
            mpe.AutoPlay = Video.AutoPlay;

            if (mpe.MediaPlayer != null)
            {
                mpe.MediaPlayer.IsLoopingEnabled = Video.IsLooping;
            }
        }
        else
        {
            var me = mediaControl as MediaElement;

            me.Stretch = Stretch.Uniform;
            me.AreTransportControlsEnabled = this.Video.ShowControls;
            me.SetPlaybackSource(playbackItem);
            me.PosterSource = new BitmapImage() { UriSource = this.Video.Images.Still.Uri };
            me.AutoPlay = Video.AutoPlay;
            me.IsLooping = Video.IsLooping;
        }
    }
}

what I however noticed was that the playback controls (aptly called Transport controls) were very flaky.. Here’s an example.. they’d work some times and not at other times (read most of the times)

I was really baffled by this behaviour.. I reworked the user control a few times. I moved to using PlayerFramework but its styling is Windows 8 and that was rather annoying trying to style.

So I decided to try creating custom transport controls. Step one I created a custom template control that just derived from MediaTransportControls

public sealed class VideoControls : MediaTransportControls
{
    public VideoControls()
    {
        this.DefaultStyleKey = typeof(VideoControls);
    }
}

Set the XAML to default MediaTransport Style.
Now instantiate this and add that to the MediaPlayerElement / MediaElement and all was good except it was looking a bit rough. So time to style it like my design team wanted it to look. Here’s the final style

VideoControls controls = new VideoControls();

mpe.TransportControls = controls;

<Style TargetType="view:VideoControls">
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="FlowDirection" Value="LeftToRight" />
    <Setter Property="UseSystemFocusVisuals" Value="True" />
    <Setter Property="IsTextScaleFactorEnabled" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="view:VideoControls">
                <Grid x:Name="RootGrid" Background="Transparent">
                    <Grid.Resources>

                        <!-- New AppBar button style 48x48 pixels in size -->

<Style x:Key="AppBarButtonStyle" TargetType="AppBarButton">
                            <Setter Property="Width" Value="{ThemeResource MTCMediaButtonWidth}" />
                            <Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
                            <Setter Property="Background" Value="#FF919191" />
                            <Setter Property="Foreground" Value="#FFFFFFFF" />
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="AppBarButton">
                                        <Grid x:Name="Root" MinWidth="{TemplateBinding MinWidth}" MaxWidth="{TemplateBinding MaxWidth}" Background="{TemplateBinding Background}">
                                            <StackPanel x:Name="ContentRoot" MinHeight="{ThemeResource AppBarThemeCompactHeight}">
                                                <ContentPresenter x:Name="Content" Height="20" Margin="0,14,0,4" HorizontalAlignment="Stretch" AutomationProperties.AccessibilityView="Raw" Content="{TemplateBinding Icon}" Foreground="{TemplateBinding Foreground}" />
                                                <TextBlock x:Name="TextLabel" Margin="0,0,0,6" FontFamily="{TemplateBinding FontFamily}" FontSize="12" Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding Label}" TextAlignment="Center" TextWrapping="Wrap" />
                                            </StackPanel>

                                            <TextBlock x:Name="OverflowTextLabel" Margin="12,0,12,0" Padding="0,5,0,7" HorizontalAlignment="Stretch" VerticalAlignment="Center" FontFamily="{TemplateBinding FontFamily}" FontSize="15" Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding Label}" TextAlignment="Left" TextTrimming="Clip" TextWrapping="NoWrap" Visibility="Collapsed" />
                                            <VisualStateManager.VisualStateGroups>
                                                <VisualStateGroup x:Name="ApplicationViewStates">
                                                    <VisualState x:Name="FullSize" />
                                                    <VisualState x:Name="Compact">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel" Storyboard.TargetProperty="Visibility">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Overflow">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot" Storyboard.TargetProperty="Visibility">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Visibility">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="OverflowWithToggleButtons">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentRoot" Storyboard.TargetProperty="Visibility">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Visibility">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Margin">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="38,0,12,0" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                </VisualStateGroup>
                                                <VisualStateGroup x:Name="CommonStates">
                                                    <VisualState x:Name="Normal">
                                                        <Storyboard>
                                                            <PointerUpThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="PointerOver">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="#FF616161" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="#FFFFFFFF" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <PointerUpThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Pressed">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListMediumBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <PointerDownThemeAnimation Storyboard.TargetName="OverflowTextLabel" />
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Disabled">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OverflowTextLabel" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                </VisualStateGroup>
                                                <VisualStateGroup x:Name="InputModeStates">
                                                    <VisualState x:Name="InputModeDefault" />
                                                    <VisualState x:Name="TouchInputMode">
                                                        <VisualState.Setters>
                                                            <Setter Target="OverflowTextLabel.Padding" Value="0,11,0,13" />
                                                        </VisualState.Setters>
                                                    </VisualState>
                                                </VisualStateGroup>
                                            </VisualStateManager.VisualStateGroups>
                                        </Grid>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>


                        <!-- New CommandBar Style -->

<Style x:Key="CommandBarStyle" TargetType="CommandBar">
                            <Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
                            <Setter Property="Background" Value="Transparent" />
                        </Style>


                        <!-- Style for Error Message text -->

<Style x:Key="MediaTextBlockStyle" TargetType="TextBlock">
                            <Setter Property="VerticalAlignment" Value="Center" />
                            <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
                            <Setter Property="FontSize" Value="{ThemeResource MTCMediaFontSize}" />
                            <Setter Property="FontFamily" Value="{ThemeResource MTCMediaFontFamily}" />
                            <Setter Property="IsTextScaleFactorEnabled" Value="False" />
                        </Style>



                        <!-- Style for Position slider used in Media Transport Controls -->

<Style x:Key="MediaSliderStyle" TargetType="Slider">
                            <Setter Property="Background" Value="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" />
                            <Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
                            <Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                            <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
                            <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
                            <Setter Property="ManipulationMode" Value="None" />
                            <Setter Property="UseSystemFocusVisuals" Value="True" />
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="Slider">
                                        <Grid Margin="{TemplateBinding Padding}">
                                            <Grid.Resources>

<Style x:Key="SliderThumbStyle" TargetType="Thumb">
                                                    <Setter Property="BorderThickness" Value="0" />
                                                    <Setter Property="Background" Value="{ThemeResource SystemControlForegroundAccentBrush}" />
                                                    <Setter Property="Foreground" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
                                                    <Setter Property="Template">
                                                        <Setter.Value>
                                                            <ControlTemplate TargetType="Thumb">
                                                                <Ellipse x:Name="ellipse" Fill="{TemplateBinding Foreground}" Stroke="{TemplateBinding Background}" StrokeThickness="2" />
                                                            </ControlTemplate>
                                                        </Setter.Value>
                                                    </Setter>
                                                </Style>


<Style x:Key="MediaSliderProgressBarStyle" TargetType="ProgressBar">
                                                    <Setter Property="Height" Value="{ThemeResource SliderTrackThemeHeight}" />
                                                    <Setter Property="Minimum" Value="0" />
                                                    <Setter Property="Maximum" Value="100" />
                                                    <Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
                                                    <Setter Property="Background" Value="Transparent" />
                                                    <Setter Property="BorderBrush" Value="Transparent" />
                                                    <Setter Property="BorderThickness" Value="1" />
                                                </Style>

                                            </Grid.Resources>
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="Auto" />
                                                <RowDefinition Height="*" />
                                            </Grid.RowDefinitions>
                                            <ContentPresenter x:Name="HeaderContentPresenter" Margin="{ThemeResource SliderHeaderThemeMargin}" x:DeferLoadStrategy="Lazy" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" FontWeight="{ThemeResource SliderHeaderThemeFontWeight}" Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}" Visibility="Collapsed" />
                                            <Grid x:Name="SliderContainer" Grid.Row="1" Background="Transparent" Control.IsTemplateFocusTarget="True">
                                                <Grid x:Name="HorizontalTemplate" MinHeight="44">
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition Width="Auto" />
                                                        <ColumnDefinition Width="Auto" />
                                                        <ColumnDefinition Width="*" />
                                                    </Grid.ColumnDefinitions>
                                                    <Grid.RowDefinitions>
                                                        <RowDefinition Height="18" />
                                                        <RowDefinition Height="Auto" />
                                                        <RowDefinition Height="18" />
                                                    </Grid.RowDefinitions>
                                                    <Rectangle x:Name="HorizontalTrackRect" Grid.Row="1" Grid.ColumnSpan="3" Height="{ThemeResource SliderTrackThemeHeight}" Fill="{TemplateBinding Background}" />
                                                    <ProgressBar x:Name="DownloadProgressIndicator" Grid.Row="1" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Center" Style="{StaticResource MediaSliderProgressBarStyle}" />
                                                    <Rectangle x:Name="HorizontalDecreaseRect" Grid.Row="1" Fill="{TemplateBinding Foreground}" />
                                                    <TickBar x:Name="TopTickBar" Grid.ColumnSpan="3" Height="{ThemeResource SliderOutsideTickBarThemeHeight}" Margin="0,0,0,4" VerticalAlignment="Bottom" Fill="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" Visibility="Collapsed" />
                                                    <TickBar x:Name="HorizontalInlineTickBar" Grid.Row="1" Grid.ColumnSpan="3" Height="{ThemeResource SliderTrackThemeHeight}" Fill="{ThemeResource SystemControlBackgroundAltHighBrush}" Visibility="Collapsed" />
                                                    <TickBar x:Name="BottomTickBar" Grid.Row="2" Grid.ColumnSpan="3" Height="{ThemeResource SliderOutsideTickBarThemeHeight}" Margin="0,4,0,0" VerticalAlignment="Top" Fill="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" Visibility="Collapsed" />
                                                    <Thumb x:Name="HorizontalThumb" Grid.Row="0" Grid.RowSpan="3" Grid.Column="1" Width="24" Height="24" AutomationProperties.AccessibilityView="Raw" Background="{ThemeResource SystemControlForegroundAccentBrush}" DataContext="{TemplateBinding Value}" Style="{StaticResource SliderThumbStyle}" />
                                                </Grid>

                                                <Grid x:Name="VerticalTemplate" MinWidth="44" Visibility="Collapsed">
                                                    <Grid.RowDefinitions>
                                                        <RowDefinition Height="*" />
                                                        <RowDefinition Height="Auto" />
                                                        <RowDefinition Height="Auto" />
                                                    </Grid.RowDefinitions>
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition Width="18" />
                                                        <ColumnDefinition Width="Auto" />
                                                        <ColumnDefinition Width="18" />
                                                    </Grid.ColumnDefinitions>
                                                    <Rectangle x:Name="VerticalTrackRect" Grid.RowSpan="3" Grid.Column="1" Width="{ThemeResource SliderTrackThemeHeight}" Fill="{TemplateBinding Background}" />
                                                    <Rectangle x:Name="VerticalDecreaseRect" Grid.Row="2" Grid.Column="1" Fill="{TemplateBinding Foreground}" />
                                                    <TickBar x:Name="LeftTickBar" Grid.RowSpan="3" Width="{ThemeResource SliderOutsideTickBarThemeHeight}" Margin="0,0,4,0" HorizontalAlignment="Right" Fill="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" Visibility="Collapsed" />
                                                    <TickBar x:Name="VerticalInlineTickBar" Grid.RowSpan="3" Grid.Column="1" Width="{ThemeResource SliderTrackThemeHeight}" Fill="{ThemeResource SystemControlBackgroundAltHighBrush}" Visibility="Collapsed" />
                                                    <TickBar x:Name="RightTickBar" Grid.RowSpan="3" Grid.Column="2" Width="{ThemeResource SliderOutsideTickBarThemeHeight}" Margin="4,0,0,0" HorizontalAlignment="Left" Fill="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" Visibility="Collapsed" />
                                                    <Thumb x:Name="VerticalThumb" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Width="8" Height="24" AutomationProperties.AccessibilityView="Raw" Background="{ThemeResource SystemControlForegroundAccentBrush}" DataContext="{TemplateBinding Value}" Style="{StaticResource SliderThumbStyle}" />
                                                </Grid>
                                            </Grid>
                                            <VisualStateManager.VisualStateGroups>
                                                <VisualStateGroup x:Name="CommonStates">
                                                    <VisualState x:Name="Normal" />
                                                    <VisualState x:Name="Pressed">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundAccentBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundAccentBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundAccentBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundAccentBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Disabled">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledHighBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TopTickBar" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BottomTickBar" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftTickBar" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightTickBar" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="PointerOver">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
                                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                </VisualStateGroup>
                                            </VisualStateManager.VisualStateGroups>
                                        </Grid>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>


                        <!-- Style for Volume Flyout used in Media Transport Controls -->

<Style x:Key="FlyoutStyle" TargetType="FlyoutPresenter">
                            <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
                            <Setter Property="Padding" Value="0" />
                        </Style>


                    </Grid.Resources>

                    <Border x:Name="ControlPanel_ControlPanelVisibilityStates_Border">
                        <Grid x:Name="ControlPanelGrid" VerticalAlignment="Bottom" Background="Transparent" RenderTransformOrigin="0.5,0.5">
                            <Grid.RenderTransform>
                                <TranslateTransform />
                            </Grid.RenderTransform>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Border x:Name="ErrorBorder" Grid.ColumnSpan="3" Width="320" Height="96" HorizontalAlignment="Center" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Visibility="Collapsed">
                                <TextBlock x:Name="ErrorTextBlock" Margin="12" Style="{StaticResource MediaTextBlockStyle}" TextWrapping="WrapWholeWords" />
                            </Border>
                            <Border x:Name="MediaTransportControls_Timeline_Border" Grid.Row="1" Grid.Column="1" Background="Transparent">
                                <Grid x:Name="MediaTransportControls_Timeline_Grid">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition />
                                        <RowDefinition Height="Auto" />
                                    </Grid.RowDefinitions>
                                    <Slider x:Name="ProgressSlider" Height="33" MinWidth="80" Margin="12,0" VerticalAlignment="Center" IsThumbToolTipEnabled="False" Style="{StaticResource MediaSliderStyle}" />
                                    <ProgressBar x:Name="BufferingProgressBar" Height="4" Margin="0,2,0,0" VerticalAlignment="Top" IsHitTestVisible="False" IsIndeterminate="True" Visibility="Collapsed" />
                                    <Grid x:Name="TimeTextGrid" Grid.Row="1" Height="15" Margin="12,0">
                                        <TextBlock x:Name="TimeElapsedElement" Margin="0" HorizontalAlignment="Left" Style="{StaticResource MediaTextBlockStyle}" Text="00:00" />
                                        <TextBlock x:Name="TimeRemainingElement" HorizontalAlignment="Right" Style="{StaticResource MediaTextBlockStyle}" Text="00:00" />
                                    </Grid>
                                </Grid>
                            </Border>
                            <Border x:Name="LeftSidePlayBorder" Grid.Row="1" Grid.Column="0" Visibility="Collapsed">
                                <AppBarButton x:Name="PlayPauseButtonOnLeft" Margin="0" VerticalAlignment="Center" Style="{StaticResource AppBarButtonStyle}">
                                    <AppBarButton.Icon>
                                        <SymbolIcon x:Name="PlayPauseSymbolLeft" Symbol="Play" />
                                    </AppBarButton.Icon>
                                </AppBarButton>
                            </Border>
                            <Border x:Name="MediaTransportControls_Command_Border" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center">
                                <CommandBar x:Name="MediaControlsCommandBar" Margin="0,0,0,24" Style="{StaticResource CommandBarStyle}">
                                    <CommandBar.PrimaryCommands>
                                        <AppBarButton x:Name="StopButton" Icon="Stop" MediaTransportControlsHelper.DropoutOrder="1" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed" />
                                        <AppBarButton x:Name="RewindButton" MediaTransportControlsHelper.DropoutOrder="2" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="CastButton" MediaTransportControlsHelper.DropoutOrder="7" Style="{StaticResource AppBarButtonStyle}">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="ZoomButton" MediaTransportControlsHelper.DropoutOrder="6" Style="{StaticResource AppBarButtonStyle}">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="AudioTracksSelectionButton" Icon="Character" MediaTransportControlsHelper.DropoutOrder="5" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed" />
                                        <AppBarButton x:Name="PlayPauseButton" MediaTransportControlsHelper.DropoutOrder="11" Style="{StaticResource AppBarButtonStyle}">
                                            <AppBarButton.Icon>
                                                <SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="CCSelectionButton" MediaTransportControlsHelper.DropoutOrder="9" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="VolumeMuteButton" MediaTransportControlsHelper.DropoutOrder="10" Style="{StaticResource AppBarButtonStyle}">
                                            <AppBarButton.Flyout>
                                                <Flyout x:Name="VolumeFlyout" FlyoutPresenterStyle="{StaticResource FlyoutStyle}">
                                                    <StackPanel Orientation="Horizontal">
                                                        <AppBarButton x:Name="AudioMuteButton" Margin="12" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource AppBarButtonStyle}">
                                                            <AppBarButton.Icon>
                                                                <SymbolIcon x:Name="AudioMuteSymbol" Symbol="Volume" />
                                                            </AppBarButton.Icon>
                                                        </AppBarButton>
                                                        <Slider x:Name="VolumeSlider" Width="{ThemeResource MTCHorizontalVolumeSliderWidth}" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" IsThumbToolTipEnabled="False" Value="50" />
                                                        <TextBlock x:Name="VolumeValue" Width="24" Margin="12" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource MediaTextBlockStyle}" Text="{Binding ElementName=VolumeSlider, Path=Value}" />
                                                    </StackPanel>
                                                </Flyout>
                                            </AppBarButton.Flyout>
                                            <AppBarButton.Icon>
                                                <SymbolIcon x:Name="VolumeMuteSymbol" Symbol="Volume" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="FullWindowButton" MediaTransportControlsHelper.DropoutOrder="8" Style="{StaticResource AppBarButtonStyle}">
                                            <AppBarButton.Icon>
                                                <SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="FastForwardButton" MediaTransportControlsHelper.DropoutOrder="3" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                        <AppBarButton x:Name="PlaybackRateButton" MediaTransportControlsHelper.DropoutOrder="4" Style="{StaticResource AppBarButtonStyle}" Visibility="Collapsed">
                                            <AppBarButton.Icon>
                                                <FontIcon Glyph="" />
                                            </AppBarButton.Icon>
                                        </AppBarButton>
                                    </CommandBar.PrimaryCommands>
                                </CommandBar>
                            </Border>
                        </Grid>
                    </Border>

                    <VisualStateManager.VisualStateGroups>

                        <!-- ControlPanel Visibility states -->
                        <VisualStateGroup x:Name="ControlPanelVisibilityStates">
                            <VisualState x:Name="ControlPanelFadeIn">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ControlPanel_ControlPanelVisibilityStates_Border" Storyboard.TargetProperty="Opacity">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="1" />
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="ControlPanelFadeOut">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ControlPanel_ControlPanelVisibilityStates_Border" Storyboard.TargetProperty="Opacity">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ControlPanel_ControlPanelVisibilityStates_Border" Storyboard.TargetProperty="IsHitTestVisible">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="False" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <!-- ControlPanel Visibility states -->

                        <!-- Media state visual states -->
                        <VisualStateGroup x:Name="MediaStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="Buffering">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BufferingProgressBar" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Loading">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BufferingProgressBar" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation Storyboard.TargetName="ProgressSlider" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
                                    <DoubleAnimation Storyboard.TargetName="MediaControlsCommandBar" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Error">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ErrorBorder" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard />
                            </VisualState>
                        </VisualStateGroup>
                        <!-- Media state visual states -->

                        <!-- Audio Selection Button visibility states -->
                        <VisualStateGroup x:Name="AudioSelectionAvailablityStates">
                            <VisualState x:Name="AudioSelectionAvailable">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AudioTracksSelectionButton" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="AudioSelectionUnavailable" />
                        </VisualStateGroup>
                        <!-- Video volume visibility states -->

                        <!-- Closed Captioning Selection Button visibility states -->
                        <VisualStateGroup x:Name="CCSelectionAvailablityStates">
                            <VisualState x:Name="CCSelectionAvailable">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CCSelectionButton" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="CCSelectionUnavailable" />
                        </VisualStateGroup>
                        <!-- Closed Captioning visibility states -->

                        <!-- Focus states -->
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualWhite" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualBlack" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused" />
                            <VisualState x:Name="PointerFocused" />
                        </VisualStateGroup>
                        <!-- Focus states -->
                        <VisualStateGroup x:Name="MediaTransportControlMode">
                            <VisualState x:Name="NormalMode" />
                            <VisualState x:Name="CompactMode">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftSidePlayBorder" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="TimeTextGrid" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaTransportControls_Command_Border" Storyboard.TargetProperty="(Grid.Column)">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="2" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaTransportControls_Command_Border" Storyboard.TargetProperty="(Grid.Row)">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="1" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaControlsCommandBar" Storyboard.TargetProperty="Margin">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="0" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseButton" Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Collapsed</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>

                        <!-- PlayPause states -->
                        <VisualStateGroup x:Name="PlayPauseStates">
                            <VisualState x:Name="PlayState" />
                            <VisualState x:Name="PauseState">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseSymbolLeft" Storyboard.TargetProperty="Symbol">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Pause" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseSymbol" Storyboard.TargetProperty="Symbol">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Pause" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <!-- VolumeMute states -->
                        <VisualStateGroup x:Name="VolumeMuteStates">
                            <VisualState x:Name="VolumeState" />
                            <VisualState x:Name="MuteState">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AudioMuteSymbol" Storyboard.TargetProperty="Symbol">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Mute" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="VolumeMuteSymbol" Storyboard.TargetProperty="Symbol">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Mute" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <!-- FullWindow states -->
                        <VisualStateGroup x:Name="FullWindowStates">
                            <VisualState x:Name="NonFullWindowState" />
                            <VisualState x:Name="FullWindowState">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FullWindowSymbol" Storyboard.TargetProperty="Symbol">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="BackToWindow" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It looks good now.

Customise Scroll Bar in #uwp

Update (20th April 2017):

Sample app with horizontal scrollbar customisation can be found at https://github.com/hermitdave/ScrollBarCustomisation

 

Controls like ListView / GridView contain a ScrollViewer which hosts scrollable content. The ScrollViewer contains two ScrollBar controls one for each scroll type (horizontal and vertical).

Getting to a control’s template can be painful.. why not just head to
Default control styles and templates and download the templates you need

If you want to do it the hard way..
You can get to control’s ScrollViewer by right clicking on it and click Edit Template > Edit a Copy
Screenshot (39)

Looking at the template you can see the ScrollViewer but there’s little you can do right now.

<ControlTemplate TargetType="ListView">
    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
        <ScrollViewer x:Name="ScrollViewer" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" TabNavigation="{TemplateBinding TabNavigation}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}">
            <ItemsPresenter FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}" Padding="{TemplateBinding Padding}"/>
        </ScrollViewer>
    </Border>
</ControlTemplate>

You need to go and Edit the template for ScrollViewer next.
You can right click on the ListView, tap Esc key which then moves focus on ScrollViewer. Click control’s Border and right click > Edit Template > Edit a copy

<ControlTemplate TargetType="ScrollViewer">
    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
        <Grid Background="{TemplateBinding Background}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ScrollContentPresenter x:Name="ScrollContentPresenter" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" Grid.RowSpan="2"/>
            <ScrollBar x:Name="VerticalScrollBar" Grid.Column="1" HorizontalAlignment="Right" IsTabStop="False" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{TemplateBinding VerticalOffset}" ViewportSize="{TemplateBinding ViewportHeight}"/>
            <ScrollBar x:Name="HorizontalScrollBar" IsTabStop="False" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{TemplateBinding HorizontalOffset}" ViewportSize="{TemplateBinding ViewportWidth}"/>
            <Border x:Name="ScrollBarSeparator" Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}" Grid.Column="1" Grid.Row="1"/>
        </Grid>
    </Border>
</ControlTemplate>

In one instance I needed the ScrollBar to not render over the content. You can see that the ScrollViewer ScrollViewerPresenter has both ColSpan and RowSpan set. Just get rid of it so there is no overlay.
In Another instance I needed to customise it further. I needed Horizontal ScrollBar (to be of same height as the ListView) and I needed only the Left and Right buttons. So start by Setting ColSpan / RowSpan on ScrollBar as needed
Now lets get to ScrollBar template to customise it further.

<ControlTemplate x:Key="HorizontalIncrementTemplate" TargetType="RepeatButton">
    <Grid x:Name="Root">
        <Grid x:Name="HorizontalRoot" IsHitTestVisible="False">
            <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Rectangle x:Name="HorizontalTrackRect"
                        Grid.ColumnSpan="5"
                        Margin="0"
                        StrokeThickness="{ThemeResource ScrollBarTrackBorderThemeThickness}"
                        Fill="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"
                        Stroke="{ThemeResource SystemControlForegroundTransparentBrush}" />
            <RepeatButton x:Name="HorizontalSmallDecrease"
                        Grid.Column="0"
                        MinHeight="12"
                        IsTabStop="False"
                        Interval="50"
                        Margin="0"
                        Template="{StaticResource HorizontalDecrementTemplate}"
                        Width="12"
                        VerticalAlignment="Center" />
            <RepeatButton x:Name="HorizontalLargeDecrease"
                        Grid.Column="1"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        IsTabStop="False"
                        Interval="50"
                        Template="{StaticResource RepeatButtonTemplate}"
                        Width="0" />
            <Thumb x:Name="HorizontalThumb"
                        Grid.Column="2"
                        Background="{ThemeResource SystemControlForegroundChromeHighBrush}"
                        Template="{StaticResource HorizontalThumbTemplate}"
                        Height="12"
                        MinWidth="12"
                        AutomationProperties.AccessibilityView="Raw" />
            <RepeatButton x:Name="HorizontalLargeIncrease"
                        Grid.Column="3"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        IsTabStop="False"
                        Interval="50"
                        Template="{StaticResource RepeatButtonTemplate}" />
            <RepeatButton x:Name="HorizontalSmallIncrease"
                        Grid.Column="4"
                        MinHeight="12"
                        IsTabStop="False"
                        Interval="50"
                        Margin="0"
                        Template="{StaticResource HorizontalIncrementTemplate}"
                        Width="12"
                        VerticalAlignment="Center" />
        </Grid>
        <Grid x:Name="HorizontalPanningRoot" MinWidth="24">
            <Border x:Name="HorizontalPanningThumb"
                    VerticalAlignment="Bottom"
                    HorizontalAlignment="Left"
                    Background="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}"
                    BorderThickness="0"
                    Height="2"
                    MinWidth="32"
                    Margin="0,2,0,2"/>
        </Grid>
        <Grid x:Name="VerticalRoot" IsHitTestVisible="False">
            <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Rectangle x:Name="VerticalTrackRect"
                        Grid.RowSpan="5"
                        Margin="0"
                        StrokeThickness="{ThemeResource ScrollBarTrackBorderThemeThickness}"
                        Fill="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"
                        Stroke="{ThemeResource SystemControlForegroundTransparentBrush}" />
            <RepeatButton x:Name="VerticalSmallDecrease"
                        Height="12"
                        MinWidth="12"
                        IsTabStop="False"
                        Interval="50"
                        Margin="0"
                        Grid.Row="0"
                        Template="{StaticResource VerticalDecrementTemplate}"
                        HorizontalAlignment="Center" />
            <RepeatButton x:Name="VerticalLargeDecrease"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        Height="0"
                        IsTabStop="False"
                        Interval="50"
                        Grid.Row="1"
                        Template="{StaticResource RepeatButtonTemplate}" />
            <Thumb x:Name="VerticalThumb"
                        Grid.Row="2"
                        Background="{ThemeResource SystemControlForegroundChromeHighBrush}"
                        Template="{StaticResource VerticalThumbTemplate}"
                        Width="12"
                        MinHeight="12"
                        AutomationProperties.AccessibilityView="Raw" />
            <RepeatButton x:Name="VerticalLargeIncrease"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        IsTabStop="False"
                        Interval="50"
                        Grid.Row="3"
                        Template="{StaticResource RepeatButtonTemplate}" />
            <RepeatButton x:Name="VerticalSmallIncrease"
                        Height="12"
                        MinWidth="12"
                        IsTabStop="False"
                        Interval="50"
                        Margin="0"
                        Grid.Row="4"
                        Template="{StaticResource VerticalIncrementTemplate}"
                        HorizontalAlignment="Center" />
        </Grid>
        <Grid x:Name="VerticalPanningRoot" MinHeight="24">
            <Border x:Name="VerticalPanningThumb"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Right"
                    Background="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}"
                    BorderThickness="0"
                    Width="2"
                    MinHeight="32"
                    Margin="2,0,2,0"/>
        </Grid>
    </Grid>
</ControlTemplate>

In my case I only needed the two RepeatButton named HorizontalSmallDecrease and HorizontalSmallIncrease. Comment out the remaining controls (VerticalTrackRect, VerticalLargeDecrease, VerticalThumb, VerticalLargeIncrease)
For smoother scroll, modify RepeatButton’s Interval property, I set it to 5 rather than 50.

For further customisation I even created Attached Property that resizes the RepeatButton (the visibility is controlled by the ScrollBar so setting width while a hack works well.

public static class ScrollBarHelper
{
    static double InitialWidth = Double.MinValue;

    public static readonly DependencyProperty DCustomiseScrollBehaviourProperty =
    DependencyProperty.RegisterAttached("CustomiseScrollBehaviour", typeof(bool),
    typeof(ScrollBarHelper), new PropertyMetadata(false, OnCustomiseScrollBehaviourPropertyChanged));

    public static void SetCustomiseScrollBehaviour(DependencyObject d, bool value)
    {
        d.SetValue(DCustomiseScrollBehaviourProperty, value);
    }

    public static bool GetCustomiseScrollBehaviour(DependencyObject d)
    {
        return (bool)d.GetValue(DCustomiseScrollBehaviourProperty);
    }

    private static void OnCustomiseScrollBehaviourPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        if (!(bool)e.NewValue)
            return;

        var control = d as ScrollBar;
        if (control != null)
        {
            Observable.FromEventPattern<RoutedEventArgs>(control, "Loaded")
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOn(CoreDispatcherScheduler.Current)
                .Subscribe(args =>
                {
                    ProcessPosition(args.Sender);
                });

            Observable.FromEventPattern<ScrollEventArgs>(control, "Scroll")
                .Throttle(TimeSpan.FromMilliseconds(50))
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOn(CoreDispatcherScheduler.Current)
                .Subscribe(args =>
                {
                    ProcessPosition(args.Sender);
                });
        }
    }

    private static void ProcessPosition(object sender)
    {
        ScrollBar sb = (sender as ScrollBar);

        RepeatButton rbLeft = (RepeatButton)sb.FindDescendantByName("HorizontalSmallDecrease");
        RepeatButton rbRight = (RepeatButton)sb.FindDescendantByName("HorizontalSmallIncrease");

        if (rbLeft == null || rbRight == null)
            return;

        double pos = sb.Value;

        if (pos == 0 && InitialWidth == Double.MinValue)
        {
            InitialWidth = rbLeft.Width;
        }

        if (Math.Abs(pos - sb.Minimum) <= 15)
        {
            // hide the left scroll button
            rbLeft.Width = 0;
        }
        else if (Math.Abs(pos - sb.Maximum) <= 15)
        {
            // hide right scroll button
            rbRight.Width = 0;
        }
        else
        {
            rbLeft.Width = rbRight.Width = InitialWidth;
        }
    }
}

You could directly attached to ScrollBar’s Loaded / Scroll events but I used Reactive Extensions to throttle scroll changes as I wanted scroll experience to not degrade. The result.. not perfect but close to what I needed

Coding4Fun v2.0.9 released #wpdev #windev #winrt

Coding4Fun toolkit v2.0.9 for Windows Platform dev has been released and packages are available for download from Nuget.

This update builds additional support for Windows Runtime on Windows Phone 8.1 and Windows 8.1. Controls added to this release include

  • MetroFlow control (Windows 8.1 and WP 8.1)
  • Prompts (Toast, User, Message, Input, PasswordInput) for WP 8.1
  • BrushToBrushConverter now allows use of parameter to set output Opacity.

AppBarPrompt requires additional work the port wasn’t successful first time around. For issues there’s codeplex.

Happy coding