Framework Elements
WPF controls are built on the foundation of dependency objects - the DependencyObject
is at the bottom of their inheritance chain. But they also add additional functionality on top of that through another common base class, FrameworkElement
. The FrameworkElement
is involved in the layout algorithm, as well as helping to define the elements tree. Let’s add a second dependency property to our <NumberBox>
, a Value
property that will represent the value the <NumberBox>
currently represents, which will be displayed in the <TextBox>
.
We register this dependency property in much the same way as our Step
. But instead of supplying the DependencyProperty.Register()
method a PropertyMetadata
, we’ll instead supply a FrameworkPropertyMetadata
, which extends PropertyMetadata
to include additional data about how the property interacts with the WPF rendering and layout algorithms. This additional data is in the form of a bitmask defined in FrameworkPropertyMetadataOptions enumeration.
Some of the possible options are:
FrameworkPropertyMetadataOptions.AffectsMeasure
- changes to the property may affect the size of the controlFrameworkPropertyMetadataOptions.AffectsArrange
- changes to the property may affect the layout of the controlFrameworkPropertyMetadataOptions.AffectsRender
- changes to the property may affect the appearance of the controlFrameworkPropertyMetadataOptions.BindsTwoWayByDefault
- This property uses two-way bindings by default (i.e. the control is an editable control)FrameworkPropertyMetadataOptions.NotDataBindable
- This property does not allow data binding
In this case, we want a two-way binding by default, so we’ll include that flag, and also we’ll note that it affects the rendering process. Multiple flags can be combined with a bitwise OR. Constructing our FrameworkPropertyMetadata
object would then look like:
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
And registering the dependency property would be:
/// <summary>
/// Identifies the NumberBox.Value XAML attached property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(NumberBox), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
As with the Step
, we also want to declare a traditional property with the name “Value”. But instead of declaring a backing field, we will use the key/value pair stored in our DependencyObject
using GetValue()
and SetValue()
:
/// <summary>
/// The NumberBox's displayed value
/// </summary>
public double Value {
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
If we want to display the current value of Value
in the textbox of our NumberBox
control, we’ll need to bind the <TextBox>
element’s Text
property. This is accomplished in a similar fashion to the other bindings we’ve done previously, only we need to specify a RelativeSource
. This is a source relative to the control in the elements tree. We’ll specify two properties on the RelativeSource
: the Mode
which we set to FindAncestor
to search up the tree, and the AncestorType
which we set to our NumberBox
. Thus, instead of binding to the DataContext
, we’ll bind to the NumberBox
the <TextBox>
is located within. The full declaration would be:
<TextBox Grid.Column="1" Text="{Binding Path=Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:NumberBox}}"/>
Now a two-way binding exists between the Value
of the <NumberBox>
and the Text
value of the textbox. Updating either one will update the other. We’ve in effect made an editable control!