WPF Notes
Compiled by Jeremy Kelly
www.anthemion.org
Printed UNKNOWN
These are my WPF notes, covering WPF 4.5 and XAML 2006. If you find a mistake, please let me know.
Most example code follows the Split Notation.
This page includes a two-column print style. For best results, print in landscape, apply narrow margins, change the Scale setting in your browser’s print options to 70%, and enable background graphics. Firefox and Chrome each have their own set of printing bugs, but one of them usually works.
Contents
These aren't finished, but I haven't been using WPF lately, so it may be a while before I get back to them.
- XAML
- Namespaces
- Type converters
- Property elements
- Markup extensions
- Children of object elements
- Element names
- Using XAML
- WPF Infrastructure
- Dependency properties
- Value Precedence
- Attached properties
- Routed events
- Attached events
- Class handlers
- The Application instance
- Application class
- Layout
- Distance units
- Sizing controls
- Margins and padding
- Control visibility
- Control alignment
- Content overflow
- Scrolling content
- Scaling content
- Transforms
- Rotation transform
- Scale transform
- Other transforms
- Panels
- Canvas
- Stack panel
- Wrap panel
- Dock panel
- Grid
- Grid splitter
- Controls
- Window
- User input
- Keyboard input
- Mouse input
- Commands
- Routed commands
- Miscellanea
- Splash screens
- Application settings
- Sources
XAML
XAML (Extensible Application Markup Language) is an XML-derived language that documents the configuration of objects and object hierarchies, particularly UI controls. Most XAML elements represent object instances that will be instantiated at run time by calling default constructors. The relative structure of XAML elements defines parent-child relationships among the objects. The attributes for a given element often represent values to be assigned to the object's properties:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="nMain.tqWinCd" Title="Enter code">
<StackPanel>
<TextBox Name="qEdCd"/>
<Button Name="qBtnOK" Click="BtnOK_Click" Content="OK"/>
</StackPanel>
</Window>
The newest version is XAML 2009, which can be read manually with the System.Xaml
assembly introduced in .NET 4.0. The Visual Studio XAML Designer only supports XAML 2006, as do the code-behind classes produced by Visual Studio.
It is possible to instantiate generic classes with XAML, and, when this is done, the x:TypeArguments
attribute is used to specify type arguments for the generic. In XAML 2006, only root elements can be generic. To use a generic class elsewhere, create a subclass that passes type arguments to the generic in its inheritance list, and reference that in the XAML.
Namespaces
An XML namespace is declared by assigning a Uniform Resource Identifier (URI) to the xmlns
attribute. In XML, namespaces are used to resolve name conflicts, which occur when data from different sources use the same name in the same file in different ways. In XAML, they are used to associate element and attribute names with classes and class properties in some library, particularly the .NET Framework. This namespace, for example, allows elements like Window
and Button
to be mapped to their classes:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
In this case, the URI does not represent a web resource; it is merely a unique name that is known to the WPF assemblies. The XAML root element must declare at least one namespace so the root itself can be created. Namespaces declared in one element are usable in that element and in its children.
Appending a colon and an alternative name:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
produces a prefix that can be attached to names in the XAML:
<Button x:Name="BtnStop" Content="Stop"/>
WPF uses assembly attributes to associate namespaces with URIs. Assemblies that were not designed for WPF will not have the necessary attribute, but they can be used in XAML by prefixing the .NET namespace with clr-namespace
:
xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib"
The assembly name must be appended to the namespace unless the .NET namespace happens to reside in the assembly containing the XAML.
Type converters
All XAML attribute values are strings. Most properties have non-string types, so .NET defines type converters that convert strings to other types. In this example:
<Button x:Name="BtnReady" Content="Ready" Background="Gray"/>
the BrushConverter
class converts the Background
attribute to a Brush
instance. This is equivalent to:
BtnReady.Background = System.Windows.Media.Brushes.Gray;
Type conversion is enabled in the backing class by adding the TypeConverter
attribute to the property itself, or to the type that is stored in the property. Custom type converters can be created by subclassing the TypeConverter
class.
Property elements
If the type converter for a given property produces a parent class that is overly generic, or if it is necessary to initialize the object, the property can be set with a property element. This is a sub-element with a name that references the containing type, suffixed with the name of the targeted property. In this example, Button.Background
is a property element:
<Button x:Name="BtnReady" Content="Ready">
<Button.Background>
<LinearGradientBrush>
<GradientStop Color="LightGray" Offset="0.0"/>
<GradientStop Color="Gray" Offset="1.0"/>
</LinearGradientBrush>
</Button.Background>
</Button>
This example constructs a LinearGradientBrush
instance, configures it in the XAML, and assigns it to the button's Background
property.
Markup extensions
Complex objects and special values like null can be assigned to properties with markup extensions. To invoke a markup extension, place the name of the extension class and zero or more comma-delimited parameters within curly braces. This example invokes the NullExtension
class with no arguments:
BorderBrush="{x:Null}"
Markup extensions can accept positional parameters or named parameters. Positional parameters correspond to string arguments in an extension class constructor. In this example:
Content="{x:Static local:tWinMain.sTextAct}"
the string "local:tWinMain.sTextAct"
is passed to the StaticExtension
constructor.
Named parameters are name/value pairs that assign values to properties in the class. The value can itself invoke a type converter or another markup extension:
Content="{Binding Path=Title}"
Markup extension classes can also be used as values for property elements:
<Button.BorderBrush>
<x:Null/>
</Button.BorderBrush>
<Button.Content>
<x:Static Member="local:tWinMain.sTextAct"/>
</Button.Content>
When this is done, all parameters must be named.
Markup extensions are created by subclassing MarkupExtension
. It is customary for the name of such a class to end with Extension
, and, when this is done, the word Extension
can be omitted from the class name in XAML.
Children of object elements
An element representing an object can contain a child element that is:
- A type converter value;
- A
Content
value; - A collection instance.
If a type converter can convert the contained string to an instance of the containing type, that converter will be invoked. In this example, a converter creates a Cursor
instance and assigns it to the Cursor
property:
<Button.Cursor>Hand</Button.Cursor>
with this being is equivalent to:
Cursor="Hand"
Many classes use an attribute to designate one property as the content property, with this often (but not always) being named Content
. Like an implicit property element, this supports the assignment of complex objects that could not be defined in an attribute:
<Button>
<Image Source="IconPrev.png"/>
</Button>
Element children can also be used to populate a collection. If the collection implements IList
, and if an empty collection instance has already been assigned to the property (as all WPF controls do by default), then the contained instances will be added:
<ComboBox x:Name="BoxDef">
<ComboBox.Items>
<ComboBoxItem>Start</ComboBoxItem>
<ComboBoxItem>Restart</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
If the collection property happens to be the object's content property, it can be assigned without a property element:
<ComboBox x:Name="BoxDef">
<ComboBoxItem>Start</ComboBoxItem>
<ComboBoxItem>Restart</ComboBoxItem>
</ComboBox>
If the collection property does not already reference an instance, the instance can be defined and then populated in the XAML.
If the collection implements IDictionary
, the members must be associated with keys. Instead of wrapping each value instance with another class, this is done with the XAML Key
keyword:
<Application.Resources>
<Image x:Key="Begin" Source="IconBegin.png"/>
<Image x:Key="Other" Source="IconOther.png"/>
</Application.Resources>
Element names
Many WPF classes derive from FrameworkElement
or FrameworkContentElement
, both of which implement FindName
. This method can be used to locate and reference instances generated by the XAML:
var oqEdRank = oqWin.FindName("EdRank") as TextBox;
Names can be assigned to XAML elements in one of two ways. The FrameworkElement
and FrameworkContentElement
classes both define a Name
property:
Name="EdRank"
In other classes, the name can be assigned with the XAML Name
keyword:
x:Name="EdRank"
Both produce the same functionality, but only one should be used for a given instance.
Names can also be referenced from other parts of the XAML. For example, the Binding
markup extension uses the element name to support data binding:
Target="{Binding ElementName=EdRank}"
Certain type converters also use the name:
Target="EdRank"
Using XAML
In Visual Studio, adding a new window unit produces two files: a XAML file, and a C# code-behind file with the xaml.cs
extension. The code-behind is nested under the XAML file in the Solution Explorer. It contains a partial
class that derives from Window
:
namespace nMain {
partial class tWin: Window {
public tWin() {
InitializeComponent();
}
...
In general, a code-behind class must be a subclass of the XAML element type.
The Window
element in the XAML includes a Class
attribute that references the code-behind class:
x:Class="nMain.tWin"
At build time, XAML is compiled into a compact form called BAML (Binary Application Markup Language) that is stored as a resource in the containing assembly. The build also generates a C# file with the g.cs
extension within obj/Debug
or obj/Release
. This file completes the partial
definition of the code-behind class:
namespace nMain {
partial class tWin: Window, IComponentConnector {
internal System.Windows.Controls.Button BtnReady;
public void InitializeComponent() {
...
public void IComponentConnector.Connect(int aIDConn, object
aqTarg) {
...
In particular, it implements the InitializeComponent
method that is called from the constructor in the code-behind file. This method reads the resource BAML and instantiates the objects. Note that event handlers are attached to objects before properties are set, so that handlers can respond to property changes during construction. The generated definition also creates references for all named objects (including controls) contained by the window. References are associated with instances in the Connect
method, which is called as the BAML is interpreted. The references are internal
by default. To use a different access level, add x:FieldModifier
attributes to the elements.
The code-behind class is assumed to be public
. All partial
definitions must have the same access level, so the generated definition is made public
by default. To change the access level of the generated definition, add the x:ClassModifier
attribute to the root node.
If WPF is used with a language without partial
classes, a Subclass
attribute can be added to the Window
element. The generated class will take its name from Class
, and the code-behind class will inherit from that class, while taking its name from Subclass
.
XAML can also be read or written manually with the XamlReader
and XamlWriter
classes. To instantiate the structure from a XAML stream:
Window cWinFromXAML(Stream aqStm) {
var oqWin = XamlReader.Load(aqStm) as Window;
...
WPF Infrastructure
WPF (Windows Presentation Foundation) is a framework that produces themable, resolution-independent user interfaces. It is rendered with Direct3D. Most WPF classes derive from the following 'core' classes:
DispatcherObject
manages a work queue for the UI thread, somewhat like the message loop in a Win32 application. Aside from 'frozen' instances of the Freezable
class, instances should not be accessed from outside the UI thread. With this in mind, DispatcherObject
includes a Dispatcher
property that returns a Dispatcher
instance. This class provides Invoke
and BeginInvoke
methods that run delegates on the UI thread.
DependencyObject
supports dependency properties, covered below.
Visual
is used to implement 2D controls. UIElement
adds layout, control focus, event routing, and command binding functionality. FrameworkElement
adds resource, data binding, and style support, along with tooltip and context menu functionality. Control
adds additional styling features.
Visual3D
and UIElement3D
support 3D controls.
ContentElement
offers many of the functions in UIElement
, but it is used to create text elements, so it also supports text flow and wrapping. FrameworkContentElement
adds some of the functionality in FrameworkElement
. Neither class renders itself, so instances must be associated with one of the Visual
classes.
Freezable
classes support frozen and unfrozen states. When frozen, instances become read-only and (unlike other WPF instances) accessible to multiple threads. This class is typically used to implement graphics primitives.
In WPF, reference is sometimes made to logical and visual trees. A logical tree is a parent/child hierarchy that shows the instances explicitly created by the developer, whether in XAML or procedural code. A visual tree shows the instances that render those elements. Many logical tree instances will also be found in the visual tree, but some will not, and many visual tree instances will be absent from the logical tree, since they are created and contained by the logical instances.
Dependency properties
A dependency property is a .NET property backed by a DependencyProperty
instance:
class tPanStat: Control {
public static readonly DependencyProperty sCkEdProperty
= DependencyProperty.Register(
"CkEd",
typeof(bool),
typeof(tPanStat),
new PropertyMetadata(false)
);
public bool CkEd {
get { return (bool)GetValue(sCkEdProperty); }
set { SetValue(sCkEdProperty, value); }
}
...
This is used to implement data binding, UI styles, and animations, among other things.
The DependencyProperty
is expected to be public
and static
, its name must end with Property
, and it should be readonly
. It is instantiated with one of the static
Register
methods, which accept an instance of PropertyMetadata
or one of its subclasses. Metadata can be used to:
- Specify a default value for the property;
- Pass a callback that coerces input values to a valid range;
- Pass a callback that validates input, throwing if it is invalid;
- Pass a callback that responds to property changes;
- Configure other behavior, such as property value inheritance.
Metadata can be changed in a subclass by calling OverrideMetadata
from the property instance within the subclass's static
constructor.
The .NET property is known as the property wrapper. The getter passes the DependencyProperty
to GetValue
, while the setter passes it to SetValue
; these methods are implemented by DependencyObject
, from which the containing class must derive. The property wrapper is called by the XAML compiler, but WPF calls GetValue
and SetValue
directly, so the wrapper should do nothing more than call those functions.
Value Precedence
When a dependency property is read, possible values are drawn from many sources. WPF starts by finding the base value, which it selects from the object tree. In order of precedence, this value is one of:
-
The local value, produced by writing to the dependency property with its wrapper or with
SetValue
. A local value can be removed by passing theDependencyProperty
instance to theClearValue
function inDependencyObject
. - The parent template value. A template is used to replace the object tree that implements a given control. If the dependency property is attached to an element within such a tree, the value may be drawn from the control that uses the template, or from a trigger attached to that control.
- The style value. A style is a collection of property values that can be applied to multiple control instances. The value can be drawn from triggers attached to the style, triggers attached to templates within the style, or from style setters.
- The default style or theme style value. Every WPF control has a default style that defines its basic appearance. Because this style varies with the Windows theme, it is also known as the theme style. The value can be drawn from the style, or from a trigger attached to the style.
-
The inherited value. As explained below, an attached dependency property can be used to associate a value with an instance that does not itself define the property. Attached properties can also be configured to 'inherit' values from parent elements in the XAML tree, as
FontSize
does. - The default value specified in the dependency property metadata.
After the base value is selected, it is replaced or modified by animations, if any target the property being read. The animated value is coerced and validated, if such callbacks have been defined, before finally being returned.
Attached properties
Normally, a property (like any class or structure variable) associates values with instances of the type that defines the property. An attached property, by contrast, can associate values with instances that do not themselves define it. This allows a value that is assigned to a parent element to be used by its children. In this example, assigning a font in the StackPanel
element changes the font in child elements that derive from TextElement
, despite the fact that StackPanel
provides no font property:
<StackPanel TextElement.FontFamily="Arial">
...
Conversely, attached properties allow values assigned to children to be used by a parent. In this case, specifying a dock position in the Label
changes the way the containing Dock
lays out its children, even though Label
knows nothing of the Dock
implementation:
<DockPanel>
<Label Name="LblMain" DockPanel.Dock="Top">FRAME 1/A</Label>
...
In the XAML, the property attribute is prefixed with the class that defines the attached property. This class is called the attached property provider. In general, an attached property allows unrelated elements to say something about themselves to the provider.
The provider implements the attached property by creating a dependency property with the RegisterAttached
function:
class tJob: DependencyObject {
...
class tMgrRun {
public static readonly DependencyProperty sWgtRunProperty
= DependencyProperty.RegisterAttached(
"WgtRun",
typeof(float),
typeof(tMgrRun),
new PropertyMetadata(1.0F)
);
...
It then defines public
static
accessor functions that begin with Get
or Set
, and end with the property name:
public static float GetWgtRun(tJob aqJob) {
return (float)aqJob.GetValue(sWgtRunProperty);
}
public static void SetWgtRun(tJob aqJob, float aWgt) {
aqJob.SetValue(sWgtRunProperty, aWgt);
}
Both functions accept an instance of the class to which the property will be attached. Because this class is used to call GetValue
and SetValue
, it must derive from DependencyObject
. The Set
function is called when the XAML is processed, and it can be used to set the property procedurally as well. A property wrapper can also be added to allow use as a regular dependency property.
A dependency property must be implemented as an attached property if it is to 'inherit' values from parent elements in the XAML tree. When this is done, a default should be specified to ensure that a value is always available.
Routed events
The class that defines and invokes an event is called the event sender or event source, while the classes that implement handlers are event listeners. The Button
class, for instance, sends click notifications, while a Window
that handles click events is said to listen. Raising an event entails checking an event instance for a handler, and invoking that handler if it is found. By convention, the method that does this has a name that begins with On
, followed by the event name. Thus the Button
class monitors mouse activity, and if it determines that it has been clicked, it invokes its own OnClick
method. In an ordinary .NET event, this method confirms that the handler delegate is non-null, and then invokes it:
WPF supports a similar mechanism called a routed event. Much like the dependency property, this is a .NET event backed by a WPF class, in this case RoutedEvent
:
class tSwitch: Control {
public static readonly RoutedEvent sFlipEvent
= EventManager.RegisterRoutedEvent(
"Flip",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(tSwitch)
);
public event RoutedEventHandler Flip {
add { AddHandler(sFlipEvent, value); }
remove { RemoveHandler(sFlipEvent, value); }
}
void OnFlip(tArgsEventFlip aqArgs) {
RaiseEvent(new RoutedEventArgs(sFlipEvent, this));
}
...
The event is raised by calling RaiseEvent
, which invokes the handlers, if any, that were earlier added with AddHandler
. Routed events are more flexible than ordinary .NET events. They allow event signals to pass up or down through XAML hierarchies, so that handlers can be assigned in other parts of the object tree.
The RoutedEvent
instance is expected to be public
and static
, its name must end with Event
, and it should be readonly
as well. It is instantiated with the RegisterRoutedEvent
function in EventManager
. This function accepts the RoutingStrategy
enumeration, which defines how the event travels through a XAML hierarchy:
-
Bubbling
causes the event to travel up, starting with the source element; -
Tunneling
causes the event to travel down, starting with the XAML root element, and ending with the source; -
Direct
causes the event to be handled only at the source, much like a traditional .NET event.
The .NET event is called an event wrapper, and it allows handlers to be added with operator+=
and removed with operator-=
, like other events. The adder passes the RoutedEvent
to AddHandler
, while the remover passes it to RemoveHandler
. These methods are implemented by UIElement
and ContentElement
, so the event sender must derive from one of these classes.
RoutedEventArgs
is the base class for all routed event data, an instance of which is passed to RaiseEvent
. The class offers these properties:
-
RoutedEvent
stores the event instance; -
OriginalSource
stores the visual tree element where the event originated. This is the instance that calledRaiseEvent
; -
Source
stores the logical tree element where the event originated; -
Handled
indicates whether the event has been handled.
The Bubbling
or Tunneling
process continues after Handled
has been set to true, although, in most cases, no more elements are checked for handlers. One of the AddHandler
overloads includes a handledEventsToo
parameter that allows a handler to receive events that have already been handled elsewhere.
UI input is generally signaled with Bubbling
events. In WPF, some Bubbling
events are preceded by preview events, which are Tunneling
events with names that begin with Preview
. The same RoutedEventArgs
instance is passed to both handlers, so these allow parent controls to modify event data or handle events preemptively.
In XAML, a handler is attached by specifying the event name as an attribute, with the handler name as its value. Like the event sender, the event listener must be a subclass of UIElement
or ContentElement
:
<Canvas MouseLeftButtonDown="eTrack">
...
In the handler, the control that generates the event is passed as the sender:
private void eTrack(object aqSend,
MouseButtonEventArgs aqArgs) {
...
Attached events
Any routed event can also be used as an attached event, which associates a handler with an element other than the one that defined the event. In XAML, this allows a single handler in a parent element to process events from a number of descendents:
<StackPanel CheckBox.Click="eUpd">
<CheckBox x:Name="cCkNone" Content="None"/>
<CheckBox x:Name="cCkRnd" Content="Random"/>
<CheckBox x:Name="cCkLast" Content="Last"/>
</StackPanel>
This is useful when implementing controls, since the events generated by various subsidiary elements can be handled in one place.
The event sender maintains a strong reference to the listener, so handlers must be removed from the event if the listener is to be garbage-collected. Alternatively, handlers can be added with weak references by calling the AddHandler
method in the generic WeakEventManager
class.
Class handlers
Assigning a handler with XAML (or in procedural code, with the event wrapper) creates an instance listener. The assignment relates the sender to a handler in a specific instance.
A listener that derives from DependencyObject
can also act as a class listener, which automatically handles the event whenever it crosses an instance of the class within the XAML hierarchy. No handler is assigned in the XAML; in fact, it can be configured only in procedural code. The class listener defines a static
class handler:
class tPanSwitch {
static protected void csUpd_Stats(object aqSend,
RoutedEventArgs aqArgs) {
...
which is added to the event by calling RegisterClassHandler
within the listener's static
constructor:
static tPanSwitch() {
EventManager.RegisterClassHandler(
typeof(tSwitch),
tSwitch.sFlipEvent,
new RoutedEventHandler(csUpd_Stats)
);
...
Class handlers can be added to any routed event. They are invoked before any instance handlers that might also be defined. Different handlers can be defined by different classes within the class hierarchy, and, when this is done, handlers in the most-derived classes are invoked first. Because the class handler is static
, it cannot manipulate the instance except through the event parameters.
Additionally, many UIElement
subclasses offer virtual
methods that have names beginning with OnPreview
or On
. These methods are invoked by a class handler in the base class, so overriding them allows class handling to be performed without registering a new handler. If this is done, the base implementation should be called even if the override marks the event handled.
The Application instance
When Visual Studio creates a WPF project, it defines a subclass of Application
in App.xaml.cs
:
namespace nMain {
public partial class tApp: Application {
}
}
which it references in App.xaml
:
<Application x:Class="nMain.tApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:nMain"
StartupUri="WinMain.xaml">
<Application.Resources>
</Application.Resources>
</Application>
The main window is specified with the StartupUri
property. When it builds the project, Visual Studio creates the App.g.cs
file within one of the obj
subfolders. This generated file extends the partial
subclass by adding an InitializeComponent
method, which assigns properties in the XAML to the application instance:
public void InitializeComponent() {
StartupUri = new System.Uri(
"WinMain.xaml",
System.UriKind.Relative
);
}
It also defines the Main
method, which serves as an entry point for the application:
[STAThread]
public static void Main() {
nMain.tApp oqApp = new nMain.tApp();
oqApp.InitializeComponent();
oqApp.Run();
}
To prevent Main
from being defined automatically, right-click the App.xaml
file in the Solution Explorer, select Properties, and change Build Action from ApplicationDefinition to Page.
Command-line arguments can be obtained by defining a custom Main
that accepts a string array:
...
public static void Main(string[] aqArgs) {
...
or by calling the Environment.GetCommandLineArgs
function within System
.
Application class
The base Application
class includes properties and methods such as:
-
static Current
- Returns the application instance.
-
Run
Run(Window)
-
Starts the application, then returns its exit code when it shuts down. If a
Window
instance is specified, that window will be displayed. It is also possible to specify a window by setting theStartupUri
property to reference a XAML file. -
Shutdown
Shutdown(Int32)
- Stops the application and returns the specified exit code, or zero.
-
ShutdownMode
-
Ordinarily, the application ends when the last window is closed, or when
Shutdown
is explicitly invoked. Setting this property toOnMainWindowClose
causes it to end when the main window is closed. Setting it toOnExplicitShutdown
causes it to run untilShutdown
is called. -
MainWindow
- Gets or sets the main window.
-
Windows
-
Returns references to all
Window
instances in the application. -
Properties
- Returns a dictionary storing arbitrary key/value pairs, for use throughout the application.
-
Resources
-
Gets or sets a
ResourceDictionary
instance that stores application resources. The same resources can be fetched withFindResource
orTryFindResource
.
Layout
Distance units
Windows uses its DPI setting to determine the number of physical pixels in one logical inch. The DPI was once fixed at 96, but now it can be changed to a percentage of that value. Because the physical pixel size is constant for a given display, increasing the DPI causes the logical inch to increase in physical size.
The default unit in XAML is the device-independent pixel (DIP), equal to 1/96 of a logical inch. This unit is explicitly specified by appending px
to the attribute value. Logical inches or centimeters are specified with in
or cm
. Points are specified with pt
, equal to 1/72 of a logical inch. The Windows DPI may or may not match the actual pixel density of the display, but when it does, logical inches are found to equal physical inches, point sizes on the display match sizes in print, and DIPs correspond directly to physical pixels.
Logical inches are converted to physical pixel measurements by multiplying with the DPI:
physical pixels = logical inches · DPI
Therefore, to convert device-independent pixels to physical pixels:
physical pixels = DIPs / 96 · DPI
To convert points to physical pixels:
physical pixels = points / 72 · DPI
Sizing controls
In general, when they are not stretched to fill their parent, WPF controls size themselves to fit their own content. Sizes can be explicitly set with Width
and Height
, but, if the content changes size unexpectedly, it could be truncated. Width
and Height
have default values of Double.NaN
, which can be represented in XAML as NaN
, though Auto
is preferred. These properties do not generally return the actual size within the layout.
Sizes can be constrained with MinWidth
, MaxWidth
, MinHeight
, and MaxHeight
. The Max
sizes have default values of Double.PositiveInfinity
, which can be represented in XAML with Infinity
.
DesiredSize
is a read-only property that is used by containers during layout. RenderSize
is also read-only, and it gives the actual size in the layout, as do ActualWidth
and ActualHeight
. These properties cannot be relied upon outside of the LayoutUpdated
event, however.
Margins and padding
Margin
and Padding
are Thickness
properties that control spacing outside and inside a control. The Thickness
structure stores one, two, or four doubles. In XAML, multiple values are specified with a comma-delimited list:
Padding="8,8,24,8"
Unlike CSS, the first value specifies the left measurement, or the side measurements, if two values are given.
Margins can be negative, but padding values cannot.
Control visibility
TheVisibility
property determines whether the control is visible, and whether it participates in the layout. The associated Visibility
enumeration has three values:
-
Visible
causes the control to be rendered and laid-out as usual; -
Hidden
prevents the control from being rendered, but does not remove it from the layout; -
Collapsed
prevents the control from being rendered and removes it from the layout.
Control alignment
The HorizontalAlignment
and VerticalAlignment
properties affect a control's position within the space allotted by its parent, as well as its dimensions, if they have not been explicitly set. Each property accepts an enumeration with the same name. In both cases, the default value, Stretch
, causes the control to fill the space. In HorizontalAlignment
, Left
, Center
, and Right
size the contained control to its own content, and align it within the space. In VerticalAlignment
, Bottom
, Center
, and Top
do the same.
The HorizontalContentAlignment
and VerticalContentAlignment
properties affect the the control's content in a like manner. They use the same enumerations.
The FlowDirection
property facilitates the use of right-to-left scripts, like Arabic. When set to RightToLeft
, the Left
and Right
alignment values reverse their effect. Many other properties and controls also reverse the horizontal axis when RightToLeft
is set. FlowDirection
does not itself change the direction of text.
Content overflow
A control's ClipToBounds
property determines whether content is truncated at the control's boundaries, or whether it is allowed to overflow. This property is found in all UIElement
subclasses, but a number of panels clip regardless of its value. Canvas
does allow clipping to be disabled, and other controls can be made to disable clipping by placing a Canvas
between them and their children in the object hierarchy.
Scrolling content
Any control can be made to scroll by placing it inside a ScrollViewer
instance. The VerticalScrollBarVisibility
and HorizontalScrollBarVisibility
properties in this class accept members of the ScrollBarVisibility
enumeration:
-
Visible
causes the scrollbar to be displayed at all times; -
Auto
causes the scrollbar to appear only when it is needed; -
Hidden
prevents the scrollbar from appearing, but it allows scrolling with the keyboard; -
Disabled
hides the scrollbar and disables keyboard scrolling.
When a given scrollbar is Disabled
, the viewer's content receives as much space along that axis as the viewer itself has. When any other value is set, the content receives as much space as it requests.
Some controls use ScrollViewer
instances internally, and their scrollbars can be configured by assigning VerticalScrollBarVisibility
and HorizontalScrollBarVisibility
as attached properties.
Scaling content
A control can be scaled by placing it inside a Viewbox
instance. Unlike ScaleTransform
, which scales the control to some fraction of its original size, Viewbox
scales it to fit the box. The control can be made to fit in different ways, according to the Stretch
property, which accepts a Stretch
enumeration:
-
None
leaves the content unscaled; -
Fill
scales the content to fit the width and height of the box. No whitespace will be produced, but the content's aspect ratio may change; -
Uniform
sets the content to the largest size that fits entirely inside the box without changing the content's aspect ratio. The box will not be completely filled unless the aspect ratios match; -
UniformToFill
scales the content to fill the entire box without changing the content aspect ratio. If the aspect ratios do not match, some content will overflow the box.
Scaling is also affected by the StretchDirection
property, which accepts a StretchDirection
enumeration. This determines whether the content can be scaled UpOnly
, DownOnly
, or in Both
directions, as is the default.
Transforms
WPF controls provide two Transform
properties that can be used to alter control orientation, shape, and position. LayoutTransform
is implemented in FrameworkElement
, and its transform is applied before the layout. RenderTransform
is implemented in UIElement
, and its transform applies after, so that the layout reflects the original, untransformed size and position. Both properties can be set and used together.
Though all FrameworkElement
instances provide the transform properties, controls that display non-WPF content may not implement them completely.
Rotation transform
Specific operations are represented by subclasses of Transform
. The RotateTransform
class rotates the control by Angle
degrees:
<Button x:Name="BtnLaunch" Click="BtnLaunch_Click">
<Button.RenderTransform>
<RotateTransform Angle="-90"/>
</Button.RenderTransform>
Launch
</Button>
The CenterX
and CenterY
properties in this transform set the DIP position about which the rotation occurs. This point can also be set with the RenderTransformOrigin
property in the control. Unlike CenterX
and CenterY
, this property accepts a Point
bearing unit coordinates, which range from [0.0, 0.0] in the top-left corner to [1.0, 1.0] in the bottom-right. This is useful when the control size is unknown.
The text inside the control can be rotated by replacing it with a TextBlock
:
<Button x:Name="BtnLaunch" Click="BtnLaunch_Click">
<TextBlock RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="-90" CenterX="0" CenterY="0"/>
</TextBlock.RenderTransform>
Launch
</TextBlock>
</Button>
The CenterX
, CenterY
, and RenderTransformOrigin
properties are ignored in the layout transform.
Scale transform
The ScaleTransform
class provides ScaleX
and ScaleY
properties that store scaling factors for each dimension. A value of 1.0 leaves the size unchanged. The class also implements the CenterX
and CenterY
properties, along with support for RenderTransformOrigin
. As with RotateTransform
, these specify the single, unmoving point about which the transform occurs. If a corner is selected, that point will keep its original position, and the sides opposite the corner will move away to resize the control. If an inside point is selected, all four sides will move away, but the distance they move will vary according to their original distance from the point. As a result, the inside point will maintain its unit coordinate position after the transform.
If ScaleTransform
is applied as a LayoutTransform
, and if the target element uses the Stretch
alignment, the transform will have no effect unless it enlarges the control more than Stretch
has done.
Controls typically clip their content, and this clipping is performed during the layout. Therefore, an element that is enlarged as a RenderTransform
may not be clipped as expected, and one that is reduced may show evidence of clipping even when the reduced size fits entirely within the parent.
Other transforms
Controls can also be transformed with classes such as:
-
TranslateTransform
, which moves the control horizontally and vertically, but only when applied as aRenderTransform
; -
SkewTransform
, which changes rectangles into parallelograms; -
MatrixTransform
, which applies an arbitrary matrix transformation.
A set of transforms can be applied by combining them within a TransformGroup
instance:
<Button.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
<RotateTransform Angle="-90"/>
</TransformGroup>
</Button.LayoutTransform>
Panels
Like other controls, the Window
class provides a Content
property that can be set only once. To display more than one control, a Panel
must be added to the window. WPF offers a range of Panel
subclasses that arrange controls in different ways.
Canvas
The Canvas
panel supports absolute positioning. Controls are placed by assigning values to the Left
, Right
, Bottom
, and Top
attached properties defined in Canvas
, with each defining the distance from the specified side to the nearest side of the control. Setting an offset also causes the control to be anchored to that side, so that the distance is maintained if the Canvas
size changes. Only one side in each left/right or bottom/top pair can be anchored; setting both does not cause the control to be resized with the Canvas
:
<Canvas>
<Button x:Name="BtnStop"
Width="80" Height="40"
Canvas.Right="10" Canvas.Top="10"/>
</Canvas>
If the control has a margin on an anchored side, it will be added to the distance. Margins on non-anchored sides will be ignored. Neither HorizontalAlignment
nor VerticalAlignment
function within a Canvas
.
Canvas
also implements a ZIndex
attached property that controls the Z-order of overlapping controls.
Stack panel
The StackPanel
displays its children in a horizontal or vertical sequence, as determined by the Orientation
property. Each control in a vertical StackPanel
receives all the horizontal space in the panel, plus as much vertical space as the control itself requires.
VirtualizingStackPanel
is a similar control that derives from VirtualizingPanel
. When data binding is used, this panel creates controls as their cells become visible, and releases them as they go out of view. This allows large datasets to be displayed without compromising performance. The ListBox
control uses this panel internally.
Wrap panel
When its Orientation
is set to Horizontal
, the WrapPanel
displays its children in rows that wrap to new lines when they reach the right side of the panel. Additional rows are added until all children have been placed:
Setting Orientation
to Vertical
produces columns that wrap when they reach the bottom.
By default, the controls in a horizontal WrapPanel
determine their own widths, while each control's height is set to match the tallest control in its row. Setting ItemWidth
or ItemHeight
in the panel overrides these sizes, clipping the controls if necessary.
Dock panel
The DockPanel
class implements a Dock
attached property that allows child controls to attach themselves to the Left
, Right
, Bottom
, or Top
side of the panel. The controls follow the sides of the panel when it is resized:
<DockPanel>
<StackPanel DockPanel.Dock="Top" Height="40"
Orientation="Horizontal">
...
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Height="40">
...
</TextBlock>
<StackPanel Width="40" DockPanel.Dock="Left">
...
</StackPanel>
<Canvas>
...
</Canvas>
</DockPanel>
In this case, the child controls have HorizontalAlignment
or VerticalAlignment
set to Stretch
by default, so they expand to cover as much of the panel sides as possible:
The last child fills the remaining space, unless the panel's LastChildFill
property is set to false, allowing the control to be docked like other children.
Grid
The Grid
panel places controls within the cells of a table. Rows and columns are defined by adding RowDefinition
and ColumnDefinition
instances to the RowDefinitions
and ColumnDefinitions
property elements. Child controls are associated with cells by setting the Row
and Column
attached properties:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="BtnUp"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
<Button x:Name="BtnLeftDown"
Grid.Row="1" Grid.Column="0"/>
<Button x:Name="BtnRightDown"
Grid.Row="1" Grid.Column="1"/>
</Grid>
Rows and columns can be created in the XAML designer by clicking the left and right margins. If no rows are explicity defined, or no columns, one will be created automatically at run time. Controls are made to span multiple cells by setting the ColumnSpan
and RowSpan
attached properties.
Cells can be left empty, or multiple children can be added to the same cell, with later additions appearing above others in the Z-order. The Grid
class offers no way to style cells, but a cell can be colored by placing a Rectangle
instance at the bottom of its Z-order, and then setting the rectangle's Fill
. A border can be defined by setting the Stroke
property within the Rectangle
, or by placing the Rectangle
inside a Border
.
By default, each row or column receives an equal portion of the grid's height or width. This can be changed by setting the Height
or Width
properties in the row and column definitions. In XAML:
- Assigning a number produces pixel or absolute sizing, which sets the row or column to a specific DIP size;
-
Assigning
Auto
produces auto sizing, which sizes the row or column to the largest control it contains; -
Assigning '
*
' or a number followed by '*
' produces star or proportional sizing. When a single row or column is set to '*
', it consumes all space not allocated to absolute or auto-sized elements. When multiple rows or columns are set this way, the available space is split evenly between them. When '*
' is prefixed by a number, the allocation is weighted by that number.
In procedural code, the size is set by assigning a GridLength
instance to Height
or Width
. One of the GridLength
constructors accepts a GridUnitType
enumeration that selects absolute, auto, or proportional sizing.
Grid splitter
Adding a GridSplitter
allows cells to be resized at run time. By default, a splitter is displayed in only one cell, yet its effect applies to the entire row or column, so it should always be made to span the grid.
A horizontal splitter can share a row with other controls if its HorizontalAlignment
is set to Stretch
, and if its VerticalAlignment
is set to Bottom
or Top
, with this also determining the side upon which the splitter operates. When this is done, the splitter's Height
must be explicitly set to make it visible, and this height will cover other controls in the row. Similar rules apply to a vertical splitter that shares a column with grid content.
To prevent controls from being covered, it is preferable to give each splitter its own row or column. Both HorizontalAlignment
and VerticalAlignment
should be set to Stretch
, leaving the splitter thickness to be determined by the row or column thickness:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
...
<GridSplitter
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<GridSplitter
Grid.Row="0" Grid.Column="1" Grid.RowSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
Multiple rows or columns can be made to change size together by associating them with a size group. First, the IsSharedSizeScope
property in the containing Grid
must be set to true, then the SharedSizeGroup
property in the row and column definitions must be set to a common string that names the group. All rows or columns in the group will start with the same size, equal to the largest absolute or Auto
size in the group; proportional sizes will be ignored. The elements will continue to share the same size, even when one of them is resized. A group can contain both rows and columns, and it can even contain and update elements from different grids.
Controls
Window
Most Window
events are associated with virtual
handlers in the base class, so they are handled by overriding those functions.
Windows can be displayed with Show
or ShowDialog
. Show
makes the window non-modal, and returns immediately. ShowDialog
makes the window modal, and blocks until it is closed. Then it returns the value assigned to DialogResult
, or false if the window was closed with Close
. Assigning a non-null value to DialogResult
closes the window automatically. Assigning to DialogResult
after displaying with Show
causes an exception to be thrown.
The Activated
event is raised when a window is focused, and Deactivated
is raised when it loses the focus. The Activate
method focuses the window programmatically.
OnClosing
is raised when an attempt is made to close the window. Setting Cancel
within the CancelEventArgs
parameter to true keeps the window open.
Assigning a window to the Owner
property specifies that window as the parent of this one. When the parent is minimized or closed, its children are minimized or closed as well.
User input
Keyboard input
Key presses in UIElement
controls are signaled with the KeyDown
and KeyUp
events, along with Preview
variants of the same. Handlers receive a KeyEventArgs
parameter that describes the input. This class contains many properties, among which:
-
Key
returns aKey
enumeration that identifies alphanumeric, editing, navigation, function, OEM, and other key types. Number keys in the number pad are distinguished from numbers in the regular keyboard, but navigation keys in the number pad are not. Some keys (like Alt, or Esc, when Ctrl is down) have special functions in Windows, and these are always identified asKey.System
. When this happens, the specific key press can be read from theSystemKey
property; -
IsUp
andIsDown
describe the key's current state.IsToggled
tells whether keys like Caps Lock and Num Lock are enabled. TheKeyStates
property returns theKeyStates
enumeration, which combines flags that represent these three states; -
IsRepeat
tells whether the event was produced by key repeat; -
KeyboardDevice
returns aKeyboardDevice
instance. This class provides functions likeIsKeyUp
,IsKeyDown
, andIsKeyToggled
, which give the state for any key, not just the one producing the event. It also provides theModifiers
property, which tells whether any or all of the Shift, Ctrl, Alt, or Windows keys are down.
A KeyboardDevice
instance can also be obtained from the PrimaryDevice
property in the Keyboard
class, within System.Windows.Input
. This can be used at any time, even outside of keyboard events.
By default, any UIElement
can receive the keyboard focus, whether by being clicked, or tabbed-into. This can be disabled by setting the Focusable
property to false. The IsTabStop
attached property in KeyboardNavigation
allows a control to be removed from the tab order, but if Focusable
is true, it will still be possible to focus it with a click. KeyboardNavigation
also allows the tab order to be defined by setting TabIndex
.
Mouse input
Mouse movements in UIElement
controls are signaled with MouseEnter
, MouseMove
, and MouseLeave
events. Handlers for these events receive a MouseEventArgs
parameter, within which:
-
LeftButton
,MiddleButton
, andRightButton
indicate whether the mouse buttons are up or down; -
GetPosition
returns the mouse position relative to a control, or relative to the screen, if null is passed to the function; -
Timestamp
gives the time when the event occurred.
The static
Mouse
class within System.Windows.Input
allows mouse data to be queried outside of mouse events, though the position cannot be obtained this way during drag-and-drop operations.
The MouseDown
and MouseUp
events signal button input. Handlers for these events receive a MouseButtonEventArgs
parameter, which adds the following properties to MouseEventArgs
:
-
ChangedButton
identifies the button that generated the event; -
ClickCount
gives the number of clicks since the last time the double-click timeout expired.
UIElement
also provides MouseLeftButtonDown
, MouseLeftButtonUp
, MouseRightButtonDown
, and MouseRightButtonUp
events that duplicate some of the MouseDown
and MouseUp
functionality. Control
instances automatically raise MouseDoubleClick
events when the left mouse button is double-clicked.
Mouse wheel scrolling is signaled with the MouseWheel
event. Handlers for this event receive a MouseWheelEventArgs
parameter. This class adds a Delta
property to MouseEventArgs
that is positive when the wheel is scrolled up, and negative when it is scrolled down. The magnitude can vary with different devices.
UIElement
supports drag-and-drop operations that transfer DataObject
instances within or between apps. This is implemented with events like DragEnter
, DragOver
, DragLeave
, QueryContinueDrag
, and Drop
. A UIElement
instance can also capture the mouse with its CaptureMouse
function. This causes the UIElement
to receive all mouse events, even those raised when the pointer is outside its boundaries, until ReleaseMouseCapture
is called.
Preview
variants are offered for most mouse events. Note that control areas with null Background
, Fill
, or Stroke
properties do not generate mouse events, though Transparent
areas do. Also, Canvas
can display controls outside of its own Width
and Height
(both of which default to zero) but it does not generate mouse events outside this area.
Commands
A command is a user action packaged within some instance that implements ICommand
. In this interface:
-
CanExecute
indicates whether the action is available; -
CanExecuteChanged
signals a change to theCanExecute
value. This event should be raised by the code that causes the change; -
Execute
performs the action.
A control that triggers the command is called a command source, and this will typically implement ICommandSource
. Within this interface:
-
Command
specifies the command to be executed; -
As explained below, if the command is a routed command, event routing will begin at the element specified by
CommandTarget
, or at the focused control, ifCommandTarget
is null. If it is not a routed command, this property will be ignored; -
CommandParameter
stores an arbitraryobject
to be passed toCanExecute
andExecute
.
The command is somewhat like an event listener, while the source takes the place of the sender. Setting Command
in a source control like Button
or MenuItem
causes it to call Execute
when it is used:
<Button Command="{x:Static local:tWinMain.sCmdSort}"/>
The control also adds a handler to CanExecuteChanged
that enables or disables it. This allows a set of controls that produce the same action to be updated automatically.
Input gestures include key presses and mouse clicks, in combination with optional modifier keys. Like controls, these gestures can serve as command sources. A keyboard gesture is associated with a command by adding a KeyBinding
to the listener's InputBindings
collection. The Key
and Modifiers
can be specified separately:
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Shift+Control"
Command="{x:Static local:tWinMain.sCmdSort}"/>
</Window.InputBindings>
or together, as a Gesture
:
<Window.InputBindings>
<KeyBinding Gesture="Shift+Ctrl+S"
Command="{x:Static local:tWinMain.sCmdSort}"/>
</Window.InputBindings>
Mouse gestures are specified with a MouseBinding
.
Routed commands
The RoutedCommand
class implements ICommand
, but instead of performing the command itself, its CanExecute
and Execute
methods raise routed events that are handled somewhere within the XAML hierarchy. Routing will begin at the element specified by CommandTarget
, or at the focused control, if CommandTarget
is null.
A routed command is defined somewhat like a dependency property:
public static readonly RoutedCommand sCmdRest
= new RoutedCommand();
It is assigned to a command source much like any other command:
<Button Content="Restore"
Command="{x:Static local:tWinMain.sCmdRest}"
CommandParameter="REST:AQ380"/>
Handlers are associated with command events by adding a CommandBinding
to the listening control:
<Window.CommandBindings>
<CommandBinding
Command="{x:Static local:tWinMain.sCmdRest}"
CanExecute="eCanExecuteRest" Executed="eExecutedRest"/>
</Window.CommandBindings>
The CanExecute
handler has a void
return type, so it signals command availability by setting the CanExecute
property within its CanExecuteRoutedEventArgs
parameter.
Common routed commands are implemented as static
RoutedUICommand
instances within the ApplicationCommands
, NavigationCommand
, MediaCommands
, ComponentCommands
, and EditingCommands
classes. RoutedUICommand
adds a Text
property to RoutedCommand
that describes the command, the value of which is automatically localized to match the system language. Many predefined commands have default key bindings that are created automatically when the command is assigned:
<Button Command="ApplicationCommands.Find"/>
An unwanted key binding can be removed by setting its command to NotACommand
:
<Window.InputBindings>
<KeyBinding Key="F" Modifiers="Control"
Command="NotACommand"/>
</Window.InputBindings>
Miscellanea
Splash screens
WPF has built-in support for static splash screen images. To display one, add an image to the project, then change its Build Action to SplashScreen.
This technique will not work if a custom Main
function has been defined. In this case, it is necessary to display the screen programmatically. Add the image as before, but leave its Build Action set to Resource. Then create and Show
a SplashScreen
instance that references the image at the start of the Main
function:
[STAThread]
public static void Main() {
SplashScreen oqSplash = new SplashScreen("Splash.png");
oqSplash.Show(true);
...
Application settings
.NET and Visual Studio provide support for application settings, which store configuration data outside the executable.
Settings are defined with the Visual Studio Settings Designer, which is displayed by selecting the Settings tab in the project properties, or by double-clicking the Settings.settings
file within the Properties
folder in the Solution Explorer.
Four properties define each setting:
- The Name will be used to reference the property in the code. It can be set to any string that is valid for a C# identifier;
-
The Type can be any that is serializable to XML, or that is associated with a
TypeConverter
subclass implementingToString
andFromString
; - Scope can be set to Application, which produces a read-only property, or to User, which produces a read/write property;
- Value specifies the default value for the setting.
At design time, the Settings Designer stores settings configuration data in:
Properties\Settings.settings
within the solution folder. The data can also be found in App.config
, which merges the data from multiple settings files, if more are created. The designer also creates a Settings
class within:
Properties\Settings.Designer.cs
This class is defined in the default.Properties
namespace, with default being the application's default namespace. It provides a type-safe property for each setting in the designer. The application's Settings
instance can be obtained from the static
Default
property:
var oqSet = Settings.Default;
string oqCdReg = oqSet.CdRegDef;
int oCtZone = oqSet.CtZoneMax;
After modifying a user setting, the change must be explicitly stored with the Save
method:
oqSet.CtZoneMax = 10;
oqSet.Save();
At run time, settings data is stored in either of two XML files. Application properties are stored in assemblyName.exe.config
, which must be deployed in the folder containing the executable. This file also stores default values for user properties. It can be replaced at deployment time to change settings without rebuilding.
User properties are stored in user.config
. This file is created in the user's AppData folder when Save
is first called. The full path contains the 'company name' (actually the name of the namespace containing the application class), the assembly name, and the assembly version, among other things. As a result, if the assembly version changes, a new user settings file is created. To retain values stored by an earlier version, it is necessary to invoke Update
on the Settings
class when the new version starts. This can be done by creating a CkUpgrade setting with a default value of true. When this setting matches the default, Upgrade
is called, and the setting is changed to false:
void WinMain_Initialized(object sqSend, EventArgs aqArgs) {
var oqSet = Settings.Default;
if (oqSet.CkUpgrade) {
oqSet.Upgrade();
oqSet.CkUpgrade = false;
}
...
It is possible to add multiple settings files to the same project. However, all settings are merged to the same assemblyName.exe.config
and user.config
files when this is done.
Sources
WPF 4.5 Unleashed
Adam Nathan
2014, Pearson Education / Sams
MSDN
Attached properties overview
,
Application Settings Overview
,
Commanding Overview
,
Dependency properties overview
,
Manage application settings
,
How to: Create a RoutedCommand
,
Routed events overview
,
Using Settings in C#: Using Alternate Sets of Settings
,
XAML Services
Retrieved June 2017 - March 2019
Stack Overflow
How do you keep user.config settings across different assembly versions in .net?
Retrieved March 2019