Skip to content

Instantly share code, notes, and snippets.

@natalie-o-perret
Last active March 1, 2019 13:23
Show Gist options
  • Save natalie-o-perret/f8999c15b52c62794a4473beb3c96252 to your computer and use it in GitHub Desktop.
Save natalie-o-perret/f8999c15b52c62794a4473beb3c96252 to your computer and use it in GitHub Desktop.
WPF Notes

WPF: a typo in WTF!

Windows Presentation Foundation

Features

  • Separation UI / Behaviour
  • MVVM
  • Intelligent Layout
  • Scalable Graphics (vectors != rasters)
  • Templates:
    • Control: how a control looks like
    • Data: how data are rendered
  • Binding
  • Styling
  • 3D
  • Triggers
  • Declarative change of state
  • Animation

Pipeline, Big Picture

  • Managed:
    • Presentation Framework
    • Presentation Core
  • Unmanaged:
    • MIL Core
  • OS:
    • DirectX

XAML

Can be either compiler or interpreted

Declarative UI (XAML: eXtensible Application Markup Language)

Namespaces

Like namespaces in .NET, scope for unique names, can also be used to map (alias) a local name to the actual namespace.

Examples
  • xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation": maps the overall WPF client / framework XAML namespace as the default, allow to use WPF elements without the prefix.
  • xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml": maps a separate XAML namespace, mapping it (typically) to the x: prefix.
  • xmlns:custom="clr-namespace:SDKSample;assembly=SDKSampleLibrary": a library containing a class, and a reference to it in project settings
  • x:Class: Partial class declaration which connects the markup to the partial class code defined behind.
<Page x:Class="WPFApplication1.MainPage"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    xmlns:custom="clr-namespace:SDKSample;assembly=SDKSampleLibrary">  
  <!-- [...] -->  
  <custom:ExampleClass/>  
<!-- [...] -->    
</Page>  

Elements

Instance of anobject and attributes are properties of the object instance. x:name not a property but defines a variable (name). If sometimes shorthand not enough (complex type), can use property element syntax as a child element.

Example
<!-- Shorthand syntax -->
<Button Content = "Click Me" Height = "30" Width = "60" /> 

<!-- Property Element Syntax -->
<Button> 
   <Button.Content>Click Me</Button.Content> 
   <Button.Height>30</Button.Height> 
   <Button.Width>60</Button.Width> 
</Button> 

Content Property

Can be used for text or other thing.

Example
<Button Content="Click Me" />

<Button>
    <TextBlock Text="Click Me" />
</Button>

Markup Extensions

Markup extensions are a XAML technique for obtaining a value that is neither a primitive nor a specific XAML type.

Basically: when value are outside of the scope of XAML process using { ... }

Custom

Interfaces
public interface IMarkupExtension
{
    object ProvideValue(IServiceProvider serviceProvider);
}

public interface IMarkupExtension<out T> : IMarkupExtension
{
    new T ProvideValue(IServiceProvider serviceProvider);
}
Example
namespace XAMLMarkupExtension
{ 
    public partial class MainWindow : Window
    { 
        public MainWindow()
        { 
            InitializeComponent(); 
        } 
    } 
	
    public class MyMarkupExtension : MarkupExtension
    { 
        public MyMarkupExtension() { } 
        public String FirstStr { get; set; } 
        public String SecondStr { get; set; }  
		
        public override object ProvideValue(IServiceProvider serviceProvider) 
        { 
            return FirstStr + " " + SecondStr; 
        } 
    } 
}
<Window x:Class = "XAMLMarkupExtension.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:my = "clr-namespace:XAMLMarkupExtension" 
   Title = "MainWindow" Height = "350" Width = "525"> 
	
   <Grid> 
      <Button Content = "{my:MyMarkupExtension FirstStr = Markup, SecondStr = Extension}" Width = "200" Height = "20" /> 
   </Grid> 
	
</Window>

Built-in

StaticResource

Example
<Window.Resources> 
    <SolidColorBrush Color = "Blue" x:Key = "myBrush">
    </SolidColorBrush> 
</Window.Resources> 

<TextBlock 
    Foreground = "{StaticResource myBrush}" 
    Text = "First Name" 
/>

Binding

Defers a property value to be a data-bound value, creating an intermediate expression object and interpreting the data context that applies to the element and its binding at run time.

Types:

  • Default
  • OneWay
  • TwoWay
  • OneWayToSource
  • OneTime
Example
public partial class MainWindow : Window {
	
    Person person = new Person { Name = "Salman", Age = 26 };
		
    public MainWindow() 
    { 
        InitializeComponent(); 
        this.DataContext = person; 
    } 
		
    private void Button_Click(object sender, RoutedEventArgs e) 
    { 
         string message = person.Name + " is " + person.Age; 
         MessageBox.Show(message); 
    } 
} 
<TextBox Text = "{Binding Name, Mode = OneWay}"/>  

DynamicResource

Provides a value for any XAML property attribute by deferring that value to be a reference to a defined resource.

Example
 <Button>
    <Button.Background>
        <SolidColorBrush Color="{DynamicResource {x:Static SystemColors.DesktopColorKey}}" />
    </Button.Background>
    Hello
</Button>

TemplateBinding

Links the value of a property in a control template to be the value of another property on the templated control.

Example
<Border Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}" ...>

can be simplified into:

<Border Padding="{TemplateBinding Padding}" ...>

x:Null

Specifies null as a value for a XAML member.

Example
<Label FontFamily="{x:Null}" />

x:Static

References any static by-value code entity that is defined in a Common Language Specification (CLS)–compliant way.

Example
<SolidColorBrush Color="{x:Static SystemColors.ControlColor}" />

x:Array

Provides general support for arrays of objects.

Example
<ListView Margin="10">
    <ListView.ItemsSource>
        <x:Array Type="{x:Type Color}">
            <Color>Black</Color>
            <Color>Blue</Color>
            <Color>Green</Color>
            <Color>Red</Color>
            <Color>White</Color>
            <Color>Yellow</Color>
        </x:Array>
    </ListView.ItemsSource>
</ListView>

Attached Properties

An attached property is intended to be used as a type of global property that is settable on any object.

One purpose of an attached property is to allow different child elements to specify unique values for a property that is actually defined in a parent element.

Example

Simple attached property

<DockPanel>
  <CheckBox DockPanel.Dock="Top">Hello</CheckBox>
</DockPanel>

<Canvas Canvas.Left="120">
    <Rectangle Canvas.ZIndex="99" Height="60" Width="60" Fill="Gold"/>
    <Rectangle Canvas.ZIndex="98" Height="50" Width="70" Fill="Coral"/>
</Canvas>

or in the behind code

var myDockPanel = new DockPanel();
var myCheckBox = new CheckBox();
myCheckBox.Content = "Hello";
myDockPanel.Children.Add(myCheckBox);
DockPanel.SetDock(myCheckBox, Dock.Top);

Dependency Registration

public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(
    "IsBubbleSource",
    typeof(Boolean),
    typeof(AquariumObject),
    new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetIsBubbleSource(UIElement element, Boolean value)
{
    element.SetValue(IsBubbleSourceProperty, value);
}
public static Boolean GetIsBubbleSource(UIElement element)
{
    return (Boolean)element.GetValue(IsBubbleSourceProperty);
}

Hierarchy

  • Object: System.Object...
  • DispatcherObject: object that have only accessed on the thread they created it
  • DependencyObject: objects supporting dependency properties
    • ^
      • Freezable: read-only state objects, can be copied to non-readonly objects (cannot unfreeze), eg. graphic primitives like bruses, pens, geometries, animations
    • ^
      • Visual: objects that have their own 2D representation
      • UIElement: visual objects supporting routed events, command binding, layouts and focus
      • FrameworkElement: add support for styles, data bindings, resources and vertical and horizontal aligments, plus tooltips and context menu
      • Control: adds Foreground, Background, FontSize, etc. properties
    • ^
      • Visual3D: see 2D version...
      • UIElement3D: same

Layouts

StackPanel

Orientation:

  • Horizontal
  • Vertical
Example
<StackPanel Orientation="Horizontal">
   <Button MinWidth="93">OK</Button>
   <Button MinWidth="93" Margin="10,0,0,0">Cancel</Button>
</StackPanel>

DockPanel

DockPanel.Dock:

  • Top
  • Bottom
  • Left
  • Right

Also: LastChildFill

Example
<DockPanel LastChildFill="True">
    <Button Content="Dock=Top" DockPanel.Dock="Top"/>
    <Button Content="Dock=Bottom" DockPanel.Dock="Bottom"/>
    <Button Content="Dock=Left"/>
    <Button Content="Dock=Right" DockPanel.Dock="Right"/>
    <Button Content="LastChildFill=True"/>
</DockPanel>

Grid

Grid.RowDefinitions:

  • RowDefinition Grid.ColumnDefinitions:
  • ColumnDefinition

Height or Width:

  • Fixed
  • Auto
  • *
Example
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="28" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="200" />
    </Grid.ColumnDefinitions>
</Grid>

WrapPanel

Orientation:

  • Horizontal
  • Vertical
Example
<WrapPanel Orientation="Horizontal">
    <Button Content="Button" />
    <Button Content="Button" />
</WrapPanel>

Canvas

Canvas.:

  • Left
  • Right
Example
<Canvas>
    <Rectangle Canvas.Left="40" Canvas.Top="31" Width="63" Height="41" Fill="Blue"  />
    <Ellipse Canvas.Left="130" Canvas.Top="79" Width="58" Height="58" Fill="Blue"  />
    <Path Canvas.Left="61" Canvas.Top="28" Width="133" Height="98" Fill="Blue" 
          Stretch="Fill" Data="M61,125 L193,28"/>
</Canvas>

Decorators

(Inherits from FrameworkElement) Always 1 child

Border

Draw a border, a background, or even both, around another element.

Example
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
    <StackPanel Margin="10">
        <Button>Button 1</Button>
        <Button Margin="0,10">Button 2</Button>
        <Button>Button 3</Button>
    </StackPanel>
</Border>

ViewBox

Scale to fit the content to the available size.

It does not resize the content, but it transforms it.

This means that also all text sizes and line widths were scaled. Its about the same behavior as if you set the Stretch property on an Image or Path to Uniform.

Example
<Button Content="Test" />
 
<Viewbox Stretch="Uniform">
    <Button Content="Test" />
</Viewbox>

BulletDecorator

Specify how to draw the bullet itself and decorate what ever you need.

Example
<BulletDecorator>
    <BulletDecorator.Bullet>
        <Polygon Margin=” 2, 0, 0, 0″ Points=”0, 5 5, 0 10, 5 5, 10″ Fill=” Blue” />
    <BulletDecorator.Bullet>
    <TextBlock Margin=”10, 0, 0, 0″ Text=”This is a bullet” />
<BulletDecorator>

Text

Readonly

Label

Inherits from ContentControl, a base class that enables the display of almost any UI imaginable.

Supports:

  • access keys
  • DataTemplate via ContentTemplate
  • Any kind of content
  • Custom Control => Template Properties

TextBlock

On the other hand, inherits directly from FrameworkElement, thus missing out on the behavior that is common to all elements inheriting from Control. The shallow inheritance hierarchy of TextBlock makes the control lighter weight than Label and better suited for simpler, noninteractive scenarios.

Supports:

  • Only strings
  • Bold and / or Italic

Editable

RichTextBox

Allows user to edit formatted text, images, tables, or other rich content.

TextBox

Requires less system resources than above and it is ideal when only plain text needs to be edited (ie. forms).

Routed Events

Event that invokes handlers on multiple listeners in element tree (instead of the just the source element).

Routing Strategies

Bubbling

Source Element => Parent

Tunelling

Parent => Source Element

Direct

Like windows but supporting class handler (static equivalent)

Commands

Command is any class which implements ICommand interface. There is a minor difference between Commands and Events. Events are defined and associated with UI Controls. But Commands are more abstract and focused on what to be done. One Command can be associated with multiple UI Controls/Options.

ICommand Interface
public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}
Relay Command Implementation
public class RelayCommand : ICommand    
{    
    private readonly Action<object> _execute;    
    private readonly Func<object, bool> _canExecute;    
        
    public event EventHandler CanExecuteChanged    
    {    
        add { CommandManager.RequerySuggested += value; }    
        remove { CommandManager.RequerySuggested -= value; }    
    }    
        
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)    
    {    
        this._execute = execute;    
        this._canExecute = canExecute;    
    }    
        
    public bool CanExecute(object parameter)    
    {    
        return this._canExecute == null || this._canExecute(parameter);    
    }    
        
    public void Execute(object parameter)    
    {    
        this._execute(parameter);    
    }    
}  
Example
<StackPanel>
    <Menu>
        <MenuItem Command="ApplicationCommands.Paste" />
    </Menu>
    <TextBox />
</StackPanel>
// Creating the UI objects
var mainStackPanel = new StackPanel();
var pasteTextBox = new TextBox();
var stackPanelMenu = new Menu();
var pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;

MVVM

Model-View - ViewModel

  • View: the XAML or equivalent implementation, Control Derived (window, page, data template[no code behind])
  • ViewModel: often set to the DataContext of the View. Does not know the view per say tho. Usually implements either:
    • INotifiyPropertyChanged
    • INotifyCollectionChanged
    • IDateErrorInfo
    • INotifyDataErrorInfo
  • Model: where the business logic goes (ie. business domain), reference onto ViewModel

Different from MVC and MVVM (mainly due to the databinding possibilities).

Resources

Static Resource

Resources which you cannot manipulate at runtime. The static resources are evaluated only once by the element which refers them during the loading of XAML.

Dynamic Resource

Resources which you can manipulate at runtime and are evaluated at runtime. If your code behind changes the resource, the elements referring resources as dynamic resources will also change.

Templates

Control Templates

Specifies the visual structure and visual behaviour of a control.

Example
<Style x:Key="DialogButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid>
                    <Ellipse Fill="{TemplateBinding Background}"
                             Stroke="{TemplateBinding BorderBrush}"/>
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center"/>
                </Grid>            
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Button Style="{StaticResource DialogButtonStyle}" />

Data Templates

A very flexible and powerful solution to replace the visual appearance of a data item in a control like ListBox, ComboBox or ListView.

If you don't specify a data template, WPF takes the default template that is just a TextBlock. If you bind complex objects to the control, it just calls ToString() on it. Within a DataTemplate, the DataContext is set the data object. So you can easily bind against the data context to display various members of your data object

Example
<!-- Without DataTemplate -->
<ListBox ItemsSource="{Binding}" /> 
 
<!-- With DataTemplate -->
<ListBox ItemsSource="{Binding}" BorderBrush="Transparent" 
         Grid.IsSharedSizeScope="True"
         HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Margin="4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Name}" FontWeight="Bold"  />
                <TextBox Grid.Column="1" Text="{Binding Value }" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

DataTemplateSelector

It would be better if we could switch the editor depending on the type of the property.

The simplest way to do this is to use a DataTemplateSelector. The DataTemplateSelector has a single method to override: SelectTemplate(object item, DependencyObject container). In this method we decide on the provided item which DataTemplate to choose.

Example
public class PropertyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultnDataTemplate { get; set; }
    public DataTemplate BooleanDataTemplate { get; set; }
    public DataTemplate EnumDataTemplate { get; set; }
 
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var dpi = item as DependencyPropertyInfo;

        if (dpi.PropertyType == typeof(bool))
        {
            return BooleanDataTemplate;
        }

        if (dpi.PropertyType.IsEnum)
        {
            return EnumDataTemplate;
        }
 
        return DefaultnDataTemplate;
    }
}
<Window x:Class="DataTemplates.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:DataTemplates"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">
 
    <Window.Resources>
 
        <!-- Default DataTemplate -->
        <DataTemplate x:Key="DefaultDataTemplate">
           ...
        </DataTemplate>
 
        <!-- DataTemplate for Booleans -->
        <DataTemplate x:Key="BooleanDataTemplate">
           ...
        </DataTemplate>
 
        <!-- DataTemplate for Enums -->
        <DataTemplate x:Key="EnumDataTemplate">
            ...
        </DataTemplate>
 
        <!-- DataTemplate Selector -->
        <l:PropertyDataTemplateSelector x:Key="templateSelector"
              DefaultnDataTemplate="{StaticResource DefaultDataTemplate}"
              BooleanDataTemplate="{StaticResource BooleanDataTemplate}" 
              EnumDataTemplate="{StaticResource EnumDataTemplate}"/>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding}" Grid.IsSharedSizeScope="True" 
                 HorizontalContentAlignment="Stretch" 
                 ItemTemplateSelector="{StaticResource templateSelector}"/>
    </Grid>
</Window>

Data Binding

Powerful way to auto-update data between the business model and the user interface

Requirements:

  • The source of a databinding can be a normal .NET property or a DependencyProperty.
  • The target property of the binding must be a DependencyProperty.
  • To make the databinding properly work, both sides of a binding must provide a change notification that tells the binding when to update the target value:
    • On normal .NET properties this is done by raising the PropertyChanged event of the INotifyPropertyChanged interface.
    • On DependencyProperties it is done by the PropertyChanged callback of the property metadata
Example
<StackPanel>
    <TextBox x:Name="txtInput" />
    <Label Content="{Binding Text, ElementName=txtInput, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>

DataContext

Every WPF control derived from FrameworkElement has a DataContext property. This property is meant to be set to the data object it visualizes. If you don't explicity define a source of a binding, it takes the data context by default.

The DataContext property inherits its value to child elements. So you can set the DataContext on a superior layout container and its value is inherited to all child elements. This is very useful if you want to build a form that is bound to multiple properties of the same data object.

Example
<StackPanel DataContext="{StaticResource myCustomer}">
    <TextBox Text="{Binding FirstName}"/>
    <TextBox Text="{Binding LastName}"/>
    <TextBox Text="{Binding Street}"/>
    <TextBox Text="{Binding City}"/>
</StackPanel>

ValueConverters

To bind two properties of different types together, you need to use a ValueConverter.

Example
<StackPanel>
    <StackPanel.Resources>
        <BooleanToVisibilityConverter x:Key="boolToVis" />
    </StackPanel.Resources>
 
    <CheckBox x:Name="chkShowDetails" Content="Show Details" />
    <StackPanel x:Name="detailsPanel" 
                Visibility="{Binding IsChecked, ElementName=chkShowDetails, Converter={StaticResource boolToVis}}">
    </StackPanel>
</StackPanel>
public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                          CultureInfo culture)
    {
        if (value is Boolean)
        {
            return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
        }
 
        return value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
                              CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Collections

Controls that inherit from ItemsControl which has a ItemsSource property.

Example
<Window x:Class="CollectionViewFilteringDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="CollectionViewDemo"
        Height="300"
        Width="450">
    <Grid Background="DarkGreen">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
 
        <TextBlock FontWeight="Bold" Foreground="White" FontSize="20">Products</TextBlock>
        <ListBox Grid.Row="1" ItemsSource="{Binding Products}" DisplayMemberPath="Name"/>
 
        <TextBlock Grid.Column="1" FontWeight="Bold" Foreground="White"
                   FontSize="20">Options</TextBlock>
        <ListBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding ProductOptions}"
                 DisplayMemberPath="Option"/>
    </Grid>
</Window>

CollectionView

A collection view is a wrapper around a collection that provides the following additional features:

  • Navigation
  • Sorting
  • Filtering
  • Grouping
Example
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ListBox ItemsSource={Binding Customers} />
</Window>
public class CustomerView
{
   public CustomerView()
   {
        DataContext = new CustomerViewModel();
   }
}
 
public class CustomerViewModel
{
    private readonly ICollectionView _customerView;
 
    public ICollectionView Customers
    {
        get { return _customerView; }
    }
 
    public CustomerViewModel()
    {
        var customers = GetCustomers();
        _customerView = CollectionViewSource.GetDefaultView(customers);
    }
}

Multithreading

Values fired by INotifyCollectionChanged are not reliably marshalled, need to leverage either:

  • Dispatcher.BeginInvoke
  • Dispatcher.Invoke
  • awaitable Dispatcher.InvokeAsync

Use the DispatcherSynchronizationContext (wrapper around Dispatcher).

Beware SynchronizationContext.Current is the current one (current thread so it can be null).

RelativeSource

Self

<TextBlock Background="Yellow" Text="{Binding RelativeSource={RelativeSource Self},Path=ActualWidth}"/>

FindAncestor

<TextBlock Background="Yellow" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=ActualWidth}"/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment