Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

Friday, February 19, 2016

Some XAML basics

This is a quick overview of some of the fundamental XAML syntax rules for creating objects and setting properties. It's stuff that's so basic, most WPF guides won't even both to go over it. But if you don't understand these things, life as a WPF developer will be difficult for you.

I had to learn all this through trial and error and inductive reasoning; hopefully spelling it out here will save someone some else the blood, sweat, tears involved.

XAML is fundamentally about describing an object tree that you want the program to construct. You can order an object of a particular type to be created by writing an XML element with a name that matches the class name. For example:

<!-- Creates a default-constructed TextBlock -->
<TextBlock />

This commands the parser to create a TextBlock object using the class's default constructor. This syntax is not limited to WPF classes; you can create anything you like this way:

<!-- Creates a default-constructed String -->
<!-- Need to have 'xmlns:system="clr-namespace:System;assembly=mscorlib"' in document header -->
<system:String />

That's great, but default construction will only get you so far. How can we specify property values for the objects we create? The full, verbose way to do this is by using what is called element syntax. This is how you would set the Text property on a TextBlock using element syntax:

<TextBlock>
   <TextBlock.Text>Hello world</TextBlock.Text>
</TextBlock>

A few things to note here:
  1. Properties are initialized using sub-elements of the class element (property elements)
  2. Property element names must be given as ClassName.PropertyName
  3. The content of the property element gives the property value
This is all well and good, but it's also a lot of typing. That's why attribute syntax was invented. Here is an equivalent way to construct this TextBlock:

<TextBlock Text="Hello world" />

Attribute syntax saves a lot of electronic trees, but it only works when the value of the property in question can be parsed from a string. It can't help you in cases like this:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

Here, the type of the ListView.ItemTemplate property is DataTemplate, and there is no way to construct a DataTemplate from a string.

This raises an interesting question: what types of objects can the XAML parser construct from strings? Strings, obviously, are trivially constructable from strings. Primitive numerics like Int32 and Double aren't much harder. But then you encounter something like this:

<TextBlock Margin="2,4,8,2" />

FrameworkElement.Margin is of type Thickness. Thickness is a struct that exposes four public properties: Left, Top, Right, and Bottom. How can the XAML parser construct this type from the string given? Does it have hard-coded knowledge of Thickness? Or maybe there's a rule that you can assign to properties in order using a comma-delimited list? It's actually neither. If you look at the source code for the Thickness class, you'll see this:

[TypeConverter(typeof (ThicknessConverter))]
public struct Thickness : IEquatable<Thickness>

The XAML parser will notice that TypeConverterAttribute, instantiate the type of converter it indicates (ThicknessConverter), and ask it to try to convert the String "2,4,8,2" into a Thickness. And ThicknessConverter knows how to do it. If you want to be able to instantiate your own types from strings in XAML, all you need to do is write an appropriate TypeConverter and attach a TypeConverterAttribute that points to it.

There's yet another technique for building objects from strings: the markup extension. The XAML for a markup extension looks like this:

<TextBlock Text="{StaticResource ApplicationName}" />

The XAML always begins with '{' and ends with '}'. The first word following the '{' is the name of a class that descends from MarkupExtension (if the class name ends with "Extension", as convention dictates, the word "Extension" can be omitted). After the class name is a space followed by a comma-delimited list of constructor arguments (if any). After the constructor arguments are optional property assignments as a comma-delimited list of "Name=Value" pairs.

When the XAML parser encounters a markup extension, it looks up and instantiates the type using any provided constructor arguments, initializes its properties as indicated, and then calls its virtual MarkupExtension.ProvideValue() method. This method returns an object, which is then assigned to the property on the left-hand side of the expression.

Markup extensions can also be nested inside of other markup extensions. For example:

<Grid IsHitTestVisible="{Binding IsOpen,
   RelativeSource={RelativeSource TemplatedParent},
   Converter={StaticResource InverseBoolConverter}}" />

Let's break down what's happening here:
  1. A Binding markup extension is instantiated, passing the string "IsOpen" to its constructor
  2. A RelativeSource markup extension is instantiated, passing the enum value RelativeSourceMode.TemplatedParent to its constructor
  3. RelativeSource.ProvideValue() is called, and the return value is assigned to the Binding.RelativeSource property
  4. A StaticResourceExtension markup extension is instantiated, passing the string "InverseBoolConverter" to its constructor
  5. StaticResourceExtension.ProvideValue() is called, and the return value is assigned to the Binding.Converter property
  6. Binding.ProvideValue() is called, and the return value is assigned to the Grid.IsHitTestVisible property
A lot going on, but it's all pretty standard and comprehensible once you understand what the syntax means.

Now that you know about markup extensions, I can make something I said earlier less untrue (this is a technique called teaching by diminishing deception). Consider this code again:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

I said before that ListView.ItemTemplate needs to be set using element syntax because there's no way to construct a DataTemplate from a string. You now know this isn't trueyou could use a markup extension. An obvious candidate is StaticResourceExtension:

<UserControl>
   <UserControl.Resources>
      <DataTemplate x:Key="MyDataTemplate" />
   </UserControl.Resources>

   <ListView ItemTemplate="{StaticResource MyDataTemplate}" />
</UserControl>

Now you know another rule of XAMLthere are always about a half dozen ways to achieve any particular goal.

Wednesday, January 27, 2016

Copy an SVG image to the clipboard from C#

Despite the title, this is really an article about putting arbitrary binary data onto the clipboard from .net.

Suppose you have the XML text corresponding to an SVG image stored in a string, and you'd like to put in on the clipboard as an image (not as text). The MIME type is "image/svg+xml", so it seems like the solution is straightforward:

string svg = GetSvg();
Clipboard.SetData("image/svg+xml", svg); // won't work

However, if you do this and then inspect the contents of the clipboard, you'll see that .net added a number of bytes in front of your xml text:


That's no good, and it means any SVG editor is going to reject an attempt to paste the image. Maybe we should try straight binary data instead of a string?

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
Clipboard.SetData("image/svg+xml", bytes); // still won't work

This gives the same result, but with a slightly different set of extra bytes prepended. The fact that the prefix changed slightly when the datatype changed suggests we might be looking at a serialization header. And indeed, if you dig through the MSDN documentation, you might eventually find this note with the Clipboard class (emphasis mine):
An object must be serializable for it to be put on the Clipboard. If you pass a non-serializable object to a Clipboard method, the method will fail without throwing an exception. See System.Runtime.Serialization for more information on serialization. If your target application requires a very specific data format, the headers added to the data in the serialization process may prevent the application from recognizing your data. To preserve your data format, add your data as a Byte array to a MemoryStream and pass the MemoryStream to the SetData method.
Aha! Take 3:

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
MemorySteam stream = new MemoryStream(bytes);
Clipboard.SetData("image/svg+xml", stream);

This, finally, will give the result you want. The same technique will work with DataObject, in case you want to copy a bitmap version of the image alongside the SVG.

Might have been nice to have that little note with Clipboard.SetData() too, but such is the life of a software developer. :)

Monday, July 20, 2015

WPF: Animate the removal of a ListViewItem from a ListView


Animating the addition of a new item to a ListView is relatively straightforward. See this question and answer on StackOverflow, for example. But doing the same thing when an item is removed is more difficult. This is because, by the time events like Unloaded go out, the control has already been removed from the tree.

The key to making this work is to add a "MarkedForDeletion" concept somewhere. The act of marking an item for deletion should trigger the animation, and the completion of the animation should trigger the actual removal from the underlying collection. I successfully implemented this idea using an attached behavior I called AnimateItemRemovalBehavior. This class exposes three public properties, all of which must be set on each ListViewItem:
  1. Storyboard of type Storyboard. This is the actual animation you want to run when an item is removed.
  2. PerformRemoval of type ICommand. This is a command that will be executed when the animation is done running. It should execute code to actually remove the element from the databound collection.
  3. IsMarkedForRemoval of type bool. Set this to true when you decide to remove an item from the list (e.g. in a button click handler). As soon as the attached behavior sees this property change to true, it will begin the animation. And when the animation is complete, the PerformRemoval command will Execute.
With that said, let's look at the code! The behavior itself looks like this:

namespace Demo
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media.Animation;

    internal class AnimateItemRemovalBehavior
    {
        public static readonly DependencyProperty StoryboardProperty =
            DependencyProperty.RegisterAttached(
                "Storyboard",
                typeof(Storyboard),
                typeof(AnimateItemRemovalBehavior),
                null);

        public static Storyboard GetStoryboard(DependencyObject o)
        {
            return o.GetValue(StoryboardProperty) as Storyboard;
        }

        public static void SetStoryboard(DependencyObject o, Storyboard value)
        {
            o.SetValue(StoryboardProperty, value);
        }



        public static readonly DependencyProperty PerformRemovalProperty =
            DependencyProperty.RegisterAttached(
                "PerformRemoval",
                typeof(ICommand),
                typeof(AnimateItemRemovalBehavior),
                null);

        public static ICommand GetPerformRemoval(DependencyObject o)
        {
            return o.GetValue(PerformRemovalProperty) as ICommand;
        }

        public static void SetPerformRemoval(DependencyObject o, ICommand value)
        {
            o.SetValue(PerformRemovalProperty, value);
        }



        public static readonly DependencyProperty IsMarkedForRemovalProperty =
            DependencyProperty.RegisterAttached(
                "IsMarkedForRemoval",
                typeof(bool),
                typeof(AnimateItemRemovalBehavior),
                new UIPropertyMetadata(OnMarkedForRemovalChanged));

        public static bool GetIsMarkedForRemoval(DependencyObject o)
        {
            return o.GetValue(IsMarkedForRemovalProperty) as bool? ?? false;
        }

        public static void SetIsMarkedForRemoval(DependencyObject o, bool value)
        {
            o.SetValue(IsMarkedForRemovalProperty, value);
        }



        private static void OnMarkedForRemovalChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            if ((e.NewValue as bool?) != true)
                return;

            var element = d as FrameworkElement;
            if (element == null)
                throw new InvalidOperationException(
                    "MarkedForRemoval can only be set on a FrameworkElement");

            var performRemoval = GetPerformRemoval(d);
            if (performRemoval == null)
                throw new InvalidOperationException(
                    "MarkedForRemoval requires PerformRemoval to be set too");

            var sb = GetStoryboard(d);
            if (sb == null)
                throw new InvalidOperationException(
                    "MarkedForRemoval requires Stoyboard to be set too");

            if (sb.IsSealed || sb.IsFrozen)
                sb = sb.Clone();

            Storyboard.SetTarget(sb, d);
            sb.Completed += (_, __) =>
            {
                var vm = element.DataContext;
                if (!performRemoval.CanExecute(vm))
                    return;
                performRemoval.Execute(vm);
            };

            sb.Begin();
        }
    }
}

And here is some XAML giving example usage:

<Window x:Class="Demo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Demo"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <local:PersonListViewModel x:Key="ViewModel" />
    </Window.Resources>
    
    <DockPanel>
        <TextBlock DockPanel.Dock="Top" Margin="0,0,0,10"
            Text="Click an item to remove it from the list" />
        
        <ListView DataContext="{StaticResource ViewModel}"
            ItemsSource="{Binding PersonList}">
            
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="local:AnimateItemRemovalBehavior.Storyboard">
                        <Setter.Value>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                    From="1.0" To="0.0" Duration="0:0:0.2" />
                            </Storyboard>
                        </Setter.Value>
                    </Setter>

                    <Setter Property="local:AnimateItemRemovalBehavior.IsMarkedForRemoval"
                        Value="{Binding IsMarkedForRemoval}" />

                    <Setter Property="local:AnimateItemRemovalBehavior.PerformRemoval"
                        Value="{Binding RelativeSource={RelativeSource FindAncestor,
                            AncestorType={x:Type ListView}},
                            Path=DataContext.RemovePersonCommand}" />
                </Style>
            </ListView.ItemContainerStyle>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}">
                        <TextBlock.InputBindings>
                            <MouseBinding MouseAction="LeftClick"
                                Command="{Binding MarkForRemovalCommand}" />
                        </TextBlock.InputBindings>
                    </TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </DockPanel>
</Window>

And finally, some example viewmodel code to go with the XAML:

namespace Demo
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Input;

    class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(
            [CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this,
                new PropertyChangedEventArgs(propertyName));
        }
    }

    public class RelayCommand<T> : ICommand where T : class
    {
        private readonly Action<T> _methodToExecute;
        private readonly Func<T, bool> _canExecuteEvaluator;

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public RelayCommand(Action<T> methodToExecute,
            Func<T, bool> canExecuteEvaluator = null)
        {
            _methodToExecute = methodToExecute;
            _canExecuteEvaluator = canExecuteEvaluator;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecuteEvaluator?.Invoke(parameter as T) ?? true;
        }

        public void Execute(object parameter)
        {
            _methodToExecute.Invoke(parameter as T);
        }
    }



    class PersonViewModel : ViewModelBase
    {
        public PersonViewModel(string name)
        {
            Name = name;
            MarkForRemovalCommand = new RelayCommand<object>(MarkForRemoval);
        }

        public string Name { get; }

        private bool _isMarkedForRemoval;
        public bool IsMarkedForRemoval
        {
            get { return _isMarkedForRemoval; }

            set
            {
                if (_isMarkedForRemoval == value)
                    return;
                _isMarkedForRemoval = value;
                OnPropertyChanged();
            }
        }

        public ICommand MarkForRemovalCommand { get; }
        private void MarkForRemoval(object argument)
        {
            IsMarkedForRemoval = true;
        }
    }



    class PersonListViewModel : ViewModelBase
    {
        public PersonListViewModel()
        {
            PersonList = new ObservableCollection<PersonViewModel>
            {
                new PersonViewModel("Fred"),
                new PersonViewModel("Barney"),
                new PersonViewModel("Wilma"),
                new PersonViewModel("Pebbles"),
                new PersonViewModel("Bam-bam")
            };

            RemovePersonCommand = new RelayCommand<PersonViewModel>(RemovePerson);
        }

        public ObservableCollection<PersonViewModel> PersonList { get; }

        public ICommand RemovePersonCommand { get; }
        private void RemovePerson(PersonViewModel person)
        {
            PersonList.Remove(person);
        }
    }



    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Saturday, July 18, 2015

WPF: Using an object as the Value in a Style Setter will seal and freeze it

The title says it all. I discovered this during an attempt to animate the addition of a ListViewItem to a ListView. I decided to do this via an attached behavior that involved an attached property of type Storyboard. The XAML looked something like this:

<Window>
   <Window.Resources>
      <Storyboard x:Key="TheAnimation" x:Shared="False">
         <DoubleAnimation From="0.0" To="1.0" Duration="0:0:0.20"
            Storyboard.TargetProperty="Opacity" />
      </Storyboard>
   </Window.Resources>
    
   <Grid>
      <ListView>
         <ListView.Resources>
            <Style TargetType="{x:Type ListViewItem}">
               <Setter Property="local:MyBehavior.Storyboard"
                  Value="{StaticResource TheAnimation}" />
            </Style>
         </ListView.Resources>
      </ListView>
   </Grid>
</Window>

As you can see, because the ListViewItems are not directly accessibly in XAML, I used a Style to set their MyBehavior.Storyboard attached properties. Then, in the C# code for the behavior, I tried to do this:

private static void StoryboardChanged(DependencyObject d,
   DependencyPropertyChangedEventArgs e)
{
   var listViewItem = d as ListViewItem;
   if (d == null)
      return;

   var sb = e.NewValue as Storyboard;
   if (sb == null)
      return;

   Storyboard.SetTarget(sb, listViewItem);
   sb.Begin();
}

But this blew up with an InvalidOperationException on the call to Storyboard.SetTarget():
Cannot set a property on object 'System.Windows.Media.Animation.Storyboard' because it is in a read-only state
And indeed, inspecting sb in the debugger showed that its IsSealed and IsFrozen properties were both set to true.

Searching around the web turned up evidence that Styles are indeed sealed by design the first time they're used. For example, this page at MSDN ("Note that once a style has been applied, it is sealed and cannot be changed. If you want to dynamically change a style that has already been applied, you must create a new style to replace the existing one"). Examining the source code confirms this, and also demonstrates that sealing a Style seals its Setters, and sealing a Setter seals its Values. So there's no mystery about what happened, but it's not at all clear why all of this sealing is being done in the first place. There are a few claims that this has to do with thread safety, for example, here and here. That seems plausible, but I wonder if there isn't another reason toothe Style node in the XAML presumably translates into a single Style object that is shared among all the objects that consume it. If one of them were to modify it (or its contents) in some way, the change would affect everyone, which is probably not what you want. This is just a guess; I haven't been able to prove it, but it makes sense. Assuming, of course, that I am correct in presuming that only one Style object is created.

In any case, in this case, I was able to solve my problem simply by cloning the Storyboard and working with the clone:

if(sb.IsSealed || sb.IsFrozen)
   sb = sb.Clone();

A more general solution might be to make the property type XyzFactory instead of Xyz, though that would obviously limit your ability to put things together in markup.

Thursday, August 14, 2014

A PropertyGrid gotcha

In a nutshell, when placed in a PropertyGrid, objects that use the same DisplayNameAttribute in different categories lead to surprising selection behavior.

I decline to call it a bug because I suspect the authors of the framework would claim this is how it's supposed to behave, but it's still the sort of thing that can trip developers up. Imagine you set a PropertyGrid's SelectedObject to an instance of this class:

class Demo
{
    [Category("Category1")]
    [DisplayName("Value")]
    public int Val1 { get; set; }

    [Category("Category2")]
    [DisplayName("Value")]
    public int Val2 { get; set; }

    [Category("Category3")]
    [DisplayName("Value")]
    public int Val3 { get; set; }
}

Sure, we're reusing the display name "Value," but since each instance is in a different category, everything should be fine, right? Well; not exactly. Under the hood, the grid determines equality between GridEntrys by comparing the combination of label, property type, and tree depth. And in this case, "label" means display name. So it doesn't matter if the actual code names of the underlying properties differas long as their display names, types, and depths are the same, the grid will consider their entries equal. One consequence of this is that if PropertyGrid.Refresh() is called while the Category3 Value is selected, the selection will move to the Category1 Value; presumably because it is the first entry in the tree where .Equals(oldSelection) returns true.

Luckily, there is a workaround. PropertyGrid omits tab characters when it prints display names, so you can simply add a different number of \t's to the beginning or end of each one to make them unique. A bit of a hack to be sure, but it does the job.

Thanks to stackoverflow for assisting me in my investigation of this issue.