UWP Custom control gotchas– Animation

Initially I wanted to add a custom transition to my control, but that hasn’t worked out.

Instead I’ve ended up with adding custom animation into ArrangeOverride. I found it best to keep a single Storyboard for all the animation, and so my code basically looks like this

private Storyboard animationStoryboard = new Storyboard();

protected override Size ArrangeOverride(Size finalSize)
{
animationStoryboard.Stop();
animationStoryboard.Children.Clear();

// Then add the animations back in
foreach (var anim in BuildAnimations())
{
animationStoryboard.Children.Add(anim);
}
animationStoryboard.Begin();
}

This unfortunately led to the animation jumping back towards its earlier values once it had finished so it was only ever 90% or so. I tried multiple things in my BuildAnimations(). e.g.

new DoubleAnimation {From=thing.X, To=x};

new DoubleAnimation {From=DependencyProperty.GetValue(thing), To=x};

new DoubleAnimation {From=DependencyProperty.ReadLocalValue(thing), To=x};

new DoubleAnimation {From=DependencyProperty.GetAnimationBaseValue(thing), To=x};

Maybe it was just that the values were not being correctly being set by the

I even tried setting the values when the animation finished…

var result = new DoubleAnimation{From=thing.X, To=x};

result.Completed += (sender, args) => { thing.X = x; };

.. but the debugger showed me that the values already were what I expected. Maybe we just need to change the way the animation sets the value…

new DoubleAnimation{From=thing.X, To=x, FillBehavior=FillBehavior.Stop };

new DoubleAnimation{From=thing.X, To=x, FillBehavior=FillBehavior.HoldEnd };

.. nope.

Finally I discovered to set the value being animated to the final value, then animate over the top, from the original value to the final!!

thing.X = x;
new DoubleAnimation{From=thing.X, To=x, FillBehavior=FillBehavior.Stop };

Advertisements

UWP Custom control gotchas– Attached property

I’ve been having fun with a custom list. In this case I wanted to provide render in the 3 dimensional space instead of 2d, i.e. a 3d canvas.

TL;DR Stackoverflow answer.

Initially there were two problems. Firstly that it isn’t quite possible to add X and Y, as it seems canvas does. So here’s how I posted the problem on StackOverflow

In Universal Windows App flavoured Xaml, I want to create a custom panel that has a very similar attached property such as Canvas.Left (or Top, Right or Bottom). So I can simply create one according to the guide

public static readonly DependencyProperty XProperty = 
    DependencyProperty.RegisterAttached("X", typeof(double), typeof(Track), null);

public static double GetX(DependencyObject d)
{
   return (double)d.GetValue(XProperty);
}
// SetX and property wrapper omitted for brevity here

Now I can write

<c:Track>
  <TextBlock Text="1" c:Track.X="1"/>
  <TextBlock Text="2" c:Track.X="2"/>
  <TextBlock Text="3" c:Track.X="3"/>
</c:Track>

And I can then use my attached values in

public override void ArrangeOverride(Size finalSize)
{
  foreach (var child in Children)
  { 
     var x = GetX(child);
     child.Arrange(CalculateArrange(x, finalSize));
  }
}

And everything is working perfectly so far.

However when we come to an ItemsControl I can do this,

<ItemsControl ItemsSource="{Binding ListOfInts}">
  <ItemsControl.ItemPanel>
    <ItemPanelTemplate>
       <Canvas/>
    </ItemPanelTemplate>
  </ItemsControl.ItemPanel>

  <ItemsControl.ItemTemplate>
    <DataTemplate x:DatatType="Int">
      <TextBlock Canvas.Left="{Binding}" Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

But if I want to do it for my own custom panel, I have to do this

<ItemsControl ItemsSource="{Binding ListOfInts}">
  <ItemsControl.ItemPanel>
    <ItemPanelTemplate>
       <Track/>   <!--Change to my panel-->
    </ItemPanelTemplate>
  </ItemsControl.ItemPanel>

 <ItemsControl.ItemContainerStyle>  <!-- need to add the attached property here -->
   <Style TargetType="ContentPresenter">
     <Setter Property="c:Track.X" Value="{Binding}"/>
   </Style>
 </ItemsControl.ItemContainerStyle>

  <ItemsControl.ItemTemplate>
    <DataTemplate x:DatatType="Int">
      <TextBlock Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Because I can’t do

public override void ArrangeOverride(Size finalSize)
{
  //Children are now contentPresenters
  foreach (ContentPresenter child in Children) 
  {
    var controlWithAttribute = child.????
    //child.Content is the same as DataContext i.e. an Int

    var x = GetX(controlWithAttribute);
    child.Arrange(CalculateArrange(, finalSize));
  }
}

This question already has an answer here:

And the best answer there was

As noted in the section Migration notes on the Setter.Value property page on MSDN, UWP/Windows Runtime does not support bindings in Style Setters.

Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn’t support a Binding usage for Setter.Value (the Binding won’t evaluate and the Setter has no effect, you won’t get errors, but you won’t get the desired result either). When you convert XAML styles from WPF or Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding-obtained values.

A workaround could be a helper class with attached properties for the source paths of the bindings. It creates the bindings in code behind in a PropertyChangedCallback of the helper property:

public class BindingHelper
{
    public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridColumnBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridRowBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static string GetGridColumnBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridColumnBindingPathProperty);
    }

    public static void SetGridColumnBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridColumnBindingPathProperty, value);
    }

    public static string GetGridRowBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridRowBindingPathProperty);
    }

    public static void SetGridRowBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridRowBindingPathProperty, value);
    }

    private static void GridBindingPathPropertyChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var propertyPath = e.NewValue as string;

        if (propertyPath != null)
        {
            var gridProperty =
                e.Property == GridColumnBindingPathProperty
                ? Grid.ColumnProperty
                : Grid.RowProperty;

            BindingOperations.SetBinding(
                obj,
                gridProperty,
                new Binding { Path = new PropertyPath(propertyPath) });
        }
    }
}

You would use them in XAML like this:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
        <Setter Property="local:BindingHelper.GridColumnBindingPath" Value="Level"/>
        <Setter Property="local:BindingHelper.GridRowBindingPath" Value="Row"/>
    </Style>
</ItemsControl.ItemContainerStyle>

I wrote a VSIX!

It’s been a long time since I extended Visual Studio, and I have to say, the experience has gotten a whole lot better.

I’ve been doing a lot of work with JSON dates (more information about this at the end, but stick with me for the journey first). The problem is that they are not very human readable. Who knows when this date is for?

{ “date”: “/Date(1346713200000+0100)/” }

So initially I came up with the idea of a tool window where I could convert them over, but a quick hunt on MSDN led me to https://docs.microsoft.com/en-gb/visualstudio/extensibility/walkthrough-displaying-quickinfo-tooltips. It looks like this would be even better.

So now I have

image

You can see the code at https://github.com/alski/JsonQuickInfo. All of the action takes place in lines 31-91 of JsonQuickInfo.cs, and yes I will refactor that very soon!!

VS2017 – Oh so you zoomed in by accident…

One of the really nice things about VS2017 is the ctrl+. functionality. It takes away a lot of the need for ReSharper, so much so that I’m currently trying to see if I can live without R# on one of my installs.

However, if you are as fat fingered as I am then you’ve probably also found the ctrl-shift+. key combination and now you have a quandry.

Firstly, your screen just zoomed in and while you quite like it, you want it to go back. Ctrl-Shift-, isn’t bound to anything that would be too obvious. So you reach for the mouse and hit the drop down and all is well again, until you fat finger again.

Maybe if you search in ctrl-q you can find out what happened? Nope.

“Zoom” seems to give no results except for search NuGet (and I’m on a very flaky mobile connection here). Nor “enlarge”, “view” argh!!!

About this point I searched the Tools->Options->Keyboard settings until I uncovered that Ctrl-Shift+. is bound to View.Zoomin, and Ctrl-Shift+, isn’t bound to anything.

So here’s the choice.

Bind Ctrl-Shift+, to View.Zoomout, or unbind Ctrl-Shift+.

Personally, I quite like this keyboard zooming lark, and just need to found out if VS syncs this setting properly…

UWP Combobox SelectedItem doesn’t bind

I’ve had problems using UWP ComboBoxes, the symptoms being that even by Binding the SelectedItem, nothing would be selected in the ComboBox when it was displayed. Sometimes this showed as, you could select something go, leave the page and return and the selection would be cleared, or even worse, it works on some visits to a page but not others when you hadn’t changed the selection.

So here’s some causes.

The collection items changes

Illistrated as an example on Stackoverflow. Basically if you re-create your collection items then it is impossible for selected item to be found in it, i.e. it looks like SelectedItem uses ReferenceEquals to find the item in the collection.

So this code has problems because it recreates the collection AND its items on every get,

public Item[] Items => new[] 
{ 
    new Item {Index = 1}, 
    new Item {Index = 2}, 
    new Item {Index = 3}, 
    new Item {Index = 4} 
};

but this is fine as the strings are constant, even though the collection changes

public string[] Strings => new[] {"1", "2", "3", "4"};

You specify a field instead of a property

I have no idea why this doesn’t work, but… if you specify a field

public string[] Strings = {"1", "2", "3", "4"};

then the binding will fail to select the items again, it needs to be specified as

public string[] Strings {get;} = {"1", "2", "3", "4"};

You specify the XAML in the wrong order

This is the massive WTF, and yet it is quite obvious really. I had the following code

<ComboBox SelectedItem="{x:Bind FirstItem}" ItemsSource="{x:Bind Items}"> ….

When you NEED to have

<ComboBox ItemsSource="{x:Bind Items}" SelectedItem="{x:Bind FirstItemMode=TwoWay, Mode=TwoWay}">

In this case, during the bind of the SelectedItem it is checked against an empty collection, and as a result, the selected Item ends up being set to null. Then the Items are bound.

Debugging

One thing that helps is that if you switch your binding to Mode=OneWay then the binding will display correctly in the ComboBox (but obviously won’t update). Which does at least give us a starting point.

First (wordpress) blog post

So once again, technology moves on and I struggle to keep up. This is an excerpt of my old blog with all the stuff I don’t want to lose, and its a pitiful amount for 10 yrs.

I might even have to write more often….