mirror of
https://github.com/Stone-Red-Code/docs-desktop.git
synced 2026-09-04 09:06:04 +02:00
Content update - Dependency property callbacks and validation (user story 1878471) (#1210)
* Add article, snippets, toc, and redirects * Minor edits * Add code comments
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: "Dependency property callbacks and validation"
|
||||
description: Learn how to implement dependency property callbacks and validation in Windows Presentation Foundation (WPF).
|
||||
ms.date: "11/11/2021"
|
||||
dev_langs:
|
||||
- "csharp"
|
||||
- "vb"
|
||||
helpviewer_keywords:
|
||||
- "dependency properties [WPF], validation"
|
||||
- "coerce-value callbacks [WPF]"
|
||||
- "callbacks [WPF], validation"
|
||||
- "dependency properties [WPF], callbacks"
|
||||
- "validation of dependency properties [WPF]"
|
||||
---
|
||||
<!--The acrolinx score was 92 on 11/11/2021-->
|
||||
|
||||
# Dependency property callbacks and validation (WPF .NET)
|
||||
|
||||
This article describes how to define a dependency property and implement dependency property callbacks. The callbacks support value validation, value coercion, and other logic that's needed when a property value changes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The article assumes a basic knowledge of dependency properties, and that you've read [Dependency properties overview](dependency-properties-overview.md). To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write WPF applications.
|
||||
|
||||
## Validate-value callbacks
|
||||
|
||||
Validate-value callbacks provide a way for you to check whether a new dependency property value is valid before it's applied by the property system. This callback raises an exception if the value doesn't meet the validation criteria.
|
||||
|
||||
Validate-value callbacks can only be assigned to a dependency property once, during property registration. When registering a dependency property, you have the option to pass a <xref:System.Windows.ValidateValueCallback> reference to the <xref:System.Windows.DependencyProperty.Register(System.String,System.Type,System.Type,System.Windows.PropertyMetadata,System.Windows.ValidateValueCallback)> method. Validate-value callbacks aren't part of property metadata, and can't be overridden.
|
||||
|
||||
The effective value of a dependency property is its applied value. The effective value is determined through [property value precedence](dependency-property-value-precedence.md) when multiple property-based inputs exist. If a validate-value callback is registered for a dependency property, the property system will invoke its validate-value callback on value change, passing in the new value as an object. Within the callback, you can cast the value object back to the type registered with the property system, and then run your validation logic on it. The callback returns `true` if the value is valid for the property, otherwise `false`.
|
||||
|
||||
If a validate-value callback returns `false`, an exception is raised and the new value is not applied. Application writers must be prepared to handle these exceptions. A common use of validate-value callbacks is validating enumeration values, or constraining numeric values when they represent measurements that have limits. Validate-value callbacks are invoked by the property system in different scenarios, including:
|
||||
|
||||
- Object initialization, which applies a default value at creation time.
|
||||
- Programmatic calls to <xref:System.Windows.DependencyObject.SetValue%2A>.
|
||||
- Metadata overrides that specify a new default value.
|
||||
|
||||
Validate-value callbacks don't have a parameter that specifies the <xref:System.Windows.DependencyObject> instance on which the new value is set. All instances of a `DependencyObject` share the same validate-value callback, so it can't be used to validate instance-specific scenarios. For more information, see <xref:System.Windows.ValidateValueCallback>.
|
||||
|
||||
The following example shows how to prevent a property, typed as <xref:System.Double>, being set to <xref:System.Double.PositiveInfinity> or <xref:System.Double.NegativeInfinity>.
|
||||
|
||||
:::code language="csharp" source="./snippets/dependency-property-callbacks-and-validation/csharp/MainWindow.xaml.cs" id="ValueValidationScenario":::
|
||||
:::code language="vb" source="./snippets/dependency-property-callbacks-and-validation/vb/MainWindow.xaml.vb" id="ValueValidationScenario":::
|
||||
|
||||
:::code language="csharp" source="./snippets/dependency-property-callbacks-and-validation/csharp/MainWindow.xaml.cs" id="TestValueValidationScenario":::
|
||||
:::code language="vb" source="./snippets/dependency-property-callbacks-and-validation/vb/MainWindow.xaml.vb" id="TestValueValidationScenario":::
|
||||
|
||||
## Property-changed callbacks
|
||||
|
||||
Property-changed callbacks notify you when the effective value of a dependency property has changed.
|
||||
|
||||
Property-changed callbacks are part of dependency property metadata. If you derive from a class that defines a dependency property, or add your class as an owner of a dependency property, you can override the metadata. When overriding metadata, you have the option to provide a new <xref:System.Windows.PropertyChangedCallback> reference. Use a property-changed callback to run logic that's needed when a property value changes.
|
||||
|
||||
Unlike validate-value callbacks, property-changed callbacks have a parameter that specifies the <xref:System.Windows.DependencyObject> instance on which the new value is set. The next example shows how a property-changed callback can use the `DependencyObject` instance reference to trigger coerce-value callbacks.
|
||||
|
||||
## Coerce-value callbacks
|
||||
|
||||
Coerce-value callbacks provide a way for you to get notified when the effective value of a dependency property is about to change, so that you can adjust the new value before it's applied. In addition to being triggered by the property system, you can invoke coerce-value callbacks from your code.
|
||||
|
||||
Coerce-value callbacks are part of dependency property metadata. If you derive from a class that defines a dependency property, or add your class as an owner of a dependency property, you can override the metadata. When overriding the metadata, you have the option to provide a reference to a new <xref:System.Windows.CoerceValueCallback>. Use a coerce-value callback to evaluate new values and coerce them when necessary. The callback returns the coerced value if coercion occurred, otherwise it returns the unaltered new value.
|
||||
|
||||
Similar to property-changed callbacks, coerce-value callbacks have a parameter that specifies the <xref:System.Windows.DependencyObject> instance on which the new value is set. The next example shows how a coerce-value callback can use a `DependencyObject` instance reference to coerce property values.
|
||||
|
||||
> [!NOTE]
|
||||
> Default property values can't be coerced. A dependency property has its default value set on object initialization, or when you clear other values using <xref:System.Windows.DependencyObject.ClearValue%2A>.
|
||||
|
||||
### Coerce-value and property-changed callbacks in combination
|
||||
|
||||
You can create dependencies between properties on an element, by using coerce-value callbacks and property-changed callbacks in combination. For example, changes in one property force coercion or re-evaluation in another dependency property. The next example shows a common scenario: three dependency properties that respectively store the current value, minimum value, and maximum value of a UI element. If the maximum value changes so that it's less than the current value, the current value is then set to the new maximum value. And, if the minimum value changes so that it's greater than the current value, the current value is then set to the new minimum value. In the example, the <xref:System.Windows.PropertyChangedCallback> for the current value explicitly invokes the <xref:System.Windows.CoerceValueCallback> for the minimum and maximum values.
|
||||
|
||||
:::code language="csharp" source="./snippets/dependency-property-callbacks-and-validation/csharp/MainWindow.xaml.cs" id="CurrentMinMaxScenario":::
|
||||
:::code language="vb" source="./snippets/dependency-property-callbacks-and-validation/vb/MainWindow.xaml.vb" id="CurrentMinMaxScenario":::
|
||||
|
||||
## Advanced callback scenarios
|
||||
|
||||
### Constraints and desired values
|
||||
|
||||
If a locally set value of a dependency property is changed through coercion, the unchanged locally set value is retained as the *desired value*. If the coercion is based on other property values, the property system will dynamically reevaluate the coercion whenever those other values change. Within the constraints of the coercion, the property system will apply a value that's closest to the desired value. Should the coercion condition no longer apply, the property system will restore the desired value—assuming no higher [precedence](/dotnet/desktop/wpf/properties/dependency-property-value-precedence#dependency-property-setting-precedence-list) value is active. The following example tests coercion in the current value, minimum value, and maximum value scenario.
|
||||
|
||||
:::code language="csharp" source="./snippets/dependency-property-callbacks-and-validation/csharp/MainWindow.xaml.cs" id="TestCurrentMinMaxScenario":::
|
||||
:::code language="vb" source="./snippets/dependency-property-callbacks-and-validation/vb/MainWindow.xaml.vb" id="TestCurrentMinMaxScenario":::
|
||||
|
||||
Fairly complex dependency scenarios can occur when you have multiple properties that are dependent on one another in a circular manner. Technically, there's nothing wrong with complex dependencies—except that a large number of re-evaluations can reduce performance. Also, complex dependencies that are exposed in the UI might confuse users. Treat <xref:System.Windows.PropertyChangedCallback> and <xref:System.Windows.CoerceValueCallback> as unambiguously as possible, and don't over-constrain.
|
||||
|
||||
### Cancel value changes
|
||||
|
||||
By returning <xref:System.Windows.DependencyProperty.UnsetValue> from a <xref:System.Windows.CoerceValueCallback>, you can reject a property value change. This mechanism is useful when a property value change is initiated asynchronously, but when it's applied is no longer valid for the current object state. Another scenario might be to selectively suppress a value change based on where it originated. In the following example, the `CoerceValueCallback` calls the <xref:System.Windows.DependencyPropertyHelper.GetValueSource%2A> method, which returns a <xref:System.Windows.ValueSource> structure with a <xref:System.Windows.BaseValueSource> enumeration that identifies the source of the new value.
|
||||
|
||||
:::code language="csharp" source="./snippets/dependency-property-callbacks-and-validation/csharp/MainWindow.xaml.cs" id="GetValueSourceScenario":::
|
||||
:::code language="vb" source="./snippets/dependency-property-callbacks-and-validation/vb/MainWindow.xaml.vb" id="GetValueSourceScenario":::
|
||||
|
||||
## See also
|
||||
|
||||
- <xref:System.Windows.ValidateValueCallback>
|
||||
- <xref:System.Windows.PropertyChangedCallback>
|
||||
- <xref:System.Windows.CoerceValueCallback>
|
||||
- [Dependency properties overview](dependency-properties-overview.md)
|
||||
- [Dependency property metadata](dependency-property-metadata.md)
|
||||
- [Custom dependency properties](custom-dependency-properties.md)
|
||||
+1
-1
@@ -18,7 +18,7 @@ When you derive from a class that defines a dependency property, you inherit the
|
||||
|
||||
## Background
|
||||
|
||||
A class that defines a dependency property can specify its characteristics in <xref:System.Windows.PropertyMetadata> or one of its derived types, such as <xref:System.Windows.FrameworkPropertyMetadata>. Examples of those characteristics are the default value and callback references that trigger on property change and/or coercion value change. Many classes that define dependency properties, specify property metadata during dependency property registration. When metadata isn't specified during registration, the WPF property system assigns a `PropertyMetadata` object with default values. Derived classes that inherit dependency properties through class inheritance have the option to override the original metadata of any dependency property. In this way, derived classes can selectively modify dependency property characteristics to meet class requirements. When calling <xref:System.Windows.DependencyProperty.OverrideMetadata(System.Type,System.Windows.PropertyMetadata)>, a derived class specifies its own type as the first parameter, and a metadata instance as the second parameter.
|
||||
A class that defines a dependency property can specify its characteristics in <xref:System.Windows.PropertyMetadata> or one of its derived types, such as <xref:System.Windows.FrameworkPropertyMetadata>. One of those characteristics is the default value of a dependency property. Many classes that define dependency properties, specify property metadata during dependency property registration. When metadata isn't specified during registration, the WPF property system assigns a `PropertyMetadata` object with default values. Derived classes that inherit dependency properties through class inheritance have the option to override the original metadata of any dependency property. In this way, derived classes can selectively modify dependency property characteristics to meet class requirements. When calling <xref:System.Windows.DependencyProperty.OverrideMetadata(System.Type,System.Windows.PropertyMetadata)>, a derived class specifies its own type as the first parameter, and a metadata instance as the second parameter.
|
||||
|
||||
A derived class that overrides metadata on a dependency property must do so before the property is placed in use by the property system. A dependency property is placed in use when any instance of the class that registers the property is instantiated. To help meet this requirement, the derived class should call <xref:System.Windows.DependencyProperty.OverrideMetadata%2A> within its static constructor. Overriding the metadata of a dependency property after its owner type is instantiated won't raise exceptions, but will result in inconsistent behaviors in the property system. Also, a derived type can't override the metadata of a dependency property more than once, and attempts to do so will raise an exception.
|
||||
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace CodeSampleCsharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<Window x:Class="CodeSampleCsharp.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Dependency property callbacks and validation" Height="200" Width="800" Loaded="RunTests">
|
||||
<Grid>
|
||||
<Label x:Name="lblMessage" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace CodeSampleCsharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml.
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow() => InitializeComponent();
|
||||
|
||||
public void RunTests(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TestValidationBehavior();
|
||||
TestCoercionBehavior();
|
||||
TestGetValueSource();
|
||||
}
|
||||
|
||||
//<TestValueValidationScenario>
|
||||
public static void TestValidationBehavior()
|
||||
{
|
||||
Gauge1 gauge = new();
|
||||
|
||||
Debug.WriteLine($"Test value validation scenario:");
|
||||
|
||||
// Set allowed value.
|
||||
gauge.CurrentReading = 5;
|
||||
Debug.WriteLine($"Current reading: {gauge.CurrentReading}");
|
||||
|
||||
try
|
||||
{
|
||||
// Set disallowed value.
|
||||
gauge.CurrentReading = double.PositiveInfinity;
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}");
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Current reading: {gauge.CurrentReading}");
|
||||
|
||||
// Current reading: 5
|
||||
// Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
|
||||
// Current reading: 5
|
||||
}
|
||||
//</TestValueValidationScenario>
|
||||
|
||||
//<TestCurrentMinMaxScenario>
|
||||
public static void TestCoercionBehavior()
|
||||
{
|
||||
Gauge2 gauge = new()
|
||||
{
|
||||
// Set initial values.
|
||||
MinReading = 0,
|
||||
MaxReading = 10,
|
||||
CurrentReading = 5
|
||||
};
|
||||
|
||||
Debug.WriteLine($"Test current/min/max values scenario:");
|
||||
|
||||
// Current reading is not coerced.
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
|
||||
|
||||
// Current reading is coerced to max value.
|
||||
gauge.MaxReading = 3;
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
|
||||
|
||||
// Current reading is coerced, but tracking back to the desired value.
|
||||
gauge.MaxReading = 4;
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
|
||||
|
||||
// Current reading reverts to the desired value.
|
||||
gauge.MaxReading = 10;
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
|
||||
|
||||
// Current reading remains at the desired value.
|
||||
gauge.MinReading = 5;
|
||||
gauge.MaxReading = 5;
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
|
||||
|
||||
// Current reading: 5 (min=0, max=10)
|
||||
// Current reading: 3 (min=0, max=3)
|
||||
// Current reading: 4 (min=0, max=4)
|
||||
// Current reading: 5 (min=0, max=10)
|
||||
// Current reading: 5 (min=5, max=5)
|
||||
}
|
||||
//</TestCurrentMinMaxScenario>
|
||||
|
||||
//<TestGetValueSourceScenario>
|
||||
public static void TestGetValueSource()
|
||||
{
|
||||
Gauge3 gauge = new()
|
||||
{
|
||||
// The GetValueSource method sees the first
|
||||
// locally set value as a default value (why?)
|
||||
CurrentReading = 5
|
||||
};
|
||||
|
||||
Debug.WriteLine($"Test GetValueSource scenario:");
|
||||
|
||||
// Locally set value.
|
||||
gauge.CurrentReading = 7;
|
||||
|
||||
// Locally set value is rejected.
|
||||
Debug.WriteLine($"Current reading: " +
|
||||
$"{gauge.CurrentReading}");
|
||||
|
||||
// Current reading: 5
|
||||
}
|
||||
//</TestGetValueSourceScenario>
|
||||
}
|
||||
|
||||
//<ValueValidationScenario>
|
||||
public class Gauge1 : Control
|
||||
{
|
||||
public Gauge1() : base() { }
|
||||
|
||||
// Register a dependency property with the specified property name,
|
||||
// property type, owner type, property metadata, and callbacks.
|
||||
public static readonly DependencyProperty CurrentReadingProperty =
|
||||
DependencyProperty.Register(
|
||||
name: "CurrentReading",
|
||||
propertyType: typeof(double),
|
||||
ownerType: typeof(Gauge1),
|
||||
typeMetadata: new FrameworkPropertyMetadata(
|
||||
defaultValue: double.NaN,
|
||||
flags: FrameworkPropertyMetadataOptions.AffectsMeasure),
|
||||
validateValueCallback: new ValidateValueCallback(IsValidReading));
|
||||
|
||||
// CLR wrapper with get/set accessors.
|
||||
public double CurrentReading
|
||||
{
|
||||
get => (double)GetValue(CurrentReadingProperty);
|
||||
set => SetValue(CurrentReadingProperty, value);
|
||||
}
|
||||
|
||||
// Validate-value callback.
|
||||
public static bool IsValidReading(object value)
|
||||
{
|
||||
double val = (double)value;
|
||||
return !val.Equals(double.NegativeInfinity) &&
|
||||
!val.Equals(double.PositiveInfinity);
|
||||
}
|
||||
}
|
||||
//</ValueValidationScenario>
|
||||
|
||||
//<CurrentMinMaxScenario>
|
||||
public class Gauge2 : Control
|
||||
{
|
||||
public Gauge2() : base() { }
|
||||
|
||||
// Register a dependency property with the specified property name,
|
||||
// property type, owner type, property metadata, and callbacks.
|
||||
public static readonly DependencyProperty CurrentReadingProperty =
|
||||
DependencyProperty.Register(
|
||||
name: "CurrentReading",
|
||||
propertyType: typeof(double),
|
||||
ownerType: typeof(Gauge2),
|
||||
typeMetadata: new FrameworkPropertyMetadata(
|
||||
defaultValue: double.NaN,
|
||||
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback: new PropertyChangedCallback(OnCurrentReadingChanged),
|
||||
coerceValueCallback: new CoerceValueCallback(CoerceCurrentReading)
|
||||
),
|
||||
validateValueCallback: new ValidateValueCallback(IsValidReading)
|
||||
);
|
||||
|
||||
// CLR wrapper with get/set accessors.
|
||||
public double CurrentReading
|
||||
{
|
||||
get => (double)GetValue(CurrentReadingProperty);
|
||||
set => SetValue(CurrentReadingProperty, value);
|
||||
}
|
||||
|
||||
// Validate-value callback.
|
||||
public static bool IsValidReading(object value)
|
||||
{
|
||||
double val = (double)value;
|
||||
return !val.Equals(double.NegativeInfinity) && !val.Equals(double.PositiveInfinity);
|
||||
}
|
||||
|
||||
// Property-changed callback.
|
||||
private static void OnCurrentReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
depObj.CoerceValue(MinReadingProperty);
|
||||
depObj.CoerceValue(MaxReadingProperty);
|
||||
}
|
||||
|
||||
// Coerce-value callback.
|
||||
private static object CoerceCurrentReading(DependencyObject depObj, object value)
|
||||
{
|
||||
Gauge2 gauge = (Gauge2)depObj;
|
||||
double currentVal = (double)value;
|
||||
currentVal = currentVal < gauge.MinReading ? gauge.MinReading : currentVal;
|
||||
currentVal = currentVal > gauge.MaxReading ? gauge.MaxReading : currentVal;
|
||||
return currentVal;
|
||||
}
|
||||
|
||||
// Register a dependency property with the specified property name,
|
||||
// property type, owner type, property metadata, and callbacks.
|
||||
public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
|
||||
name: "MaxReading",
|
||||
propertyType: typeof(double),
|
||||
ownerType: typeof(Gauge2),
|
||||
typeMetadata: new FrameworkPropertyMetadata(
|
||||
defaultValue: double.NaN,
|
||||
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback: new PropertyChangedCallback(OnMaxReadingChanged),
|
||||
coerceValueCallback: new CoerceValueCallback(CoerceMaxReading)
|
||||
),
|
||||
validateValueCallback: new ValidateValueCallback(IsValidReading)
|
||||
);
|
||||
|
||||
// CLR wrapper with get/set accessors.
|
||||
public double MaxReading
|
||||
{
|
||||
get => (double)GetValue(MaxReadingProperty);
|
||||
set => SetValue(MaxReadingProperty, value);
|
||||
}
|
||||
|
||||
// Property-changed callback.
|
||||
private static void OnMaxReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
depObj.CoerceValue(MinReadingProperty);
|
||||
depObj.CoerceValue(CurrentReadingProperty);
|
||||
}
|
||||
|
||||
// Coerce-value callback.
|
||||
private static object CoerceMaxReading(DependencyObject depObj, object value)
|
||||
{
|
||||
Gauge2 gauge = (Gauge2)depObj;
|
||||
double maxVal = (double)value;
|
||||
return maxVal < gauge.MinReading ? gauge.MinReading : maxVal;
|
||||
}
|
||||
|
||||
// Register a dependency property with the specified property name,
|
||||
// property type, owner type, property metadata, and callbacks.
|
||||
public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
|
||||
name: "MinReading",
|
||||
propertyType: typeof(double),
|
||||
ownerType: typeof(Gauge2),
|
||||
typeMetadata: new FrameworkPropertyMetadata(
|
||||
defaultValue: double.NaN,
|
||||
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback: new PropertyChangedCallback(OnMinReadingChanged),
|
||||
coerceValueCallback: new CoerceValueCallback(CoerceMinReading)
|
||||
),
|
||||
validateValueCallback: new ValidateValueCallback(IsValidReading));
|
||||
|
||||
// CLR wrapper with get/set accessors.
|
||||
public double MinReading
|
||||
{
|
||||
get => (double)GetValue(MinReadingProperty);
|
||||
set => SetValue(MinReadingProperty, value);
|
||||
}
|
||||
|
||||
// Property-changed callback.
|
||||
private static void OnMinReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
depObj.CoerceValue(MaxReadingProperty);
|
||||
depObj.CoerceValue(CurrentReadingProperty);
|
||||
}
|
||||
|
||||
// Coerce-value callback.
|
||||
private static object CoerceMinReading(DependencyObject depObj, object value)
|
||||
{
|
||||
Gauge2 gauge = (Gauge2)depObj;
|
||||
double minVal = (double)value;
|
||||
return minVal > gauge.MaxReading ? gauge.MaxReading : minVal;
|
||||
}
|
||||
}
|
||||
//</CurrentMinMaxScenario>
|
||||
|
||||
public class Gauge3 : Control
|
||||
{
|
||||
public Gauge3() : base() { }
|
||||
|
||||
// Register a dependency property with the specified property name,
|
||||
// property type, owner type, property metadata, and callbacks.
|
||||
public static readonly DependencyProperty CurrentReadingProperty =
|
||||
DependencyProperty.Register(
|
||||
name: "CurrentReading",
|
||||
propertyType: typeof(double),
|
||||
ownerType: typeof(Gauge3),
|
||||
typeMetadata: new FrameworkPropertyMetadata(
|
||||
defaultValue: double.NaN,
|
||||
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback: null,
|
||||
coerceValueCallback: new CoerceValueCallback(CoerceCurrentReading)
|
||||
)
|
||||
);
|
||||
|
||||
// CLR wrapper with get/set accessors.
|
||||
public double CurrentReading
|
||||
{
|
||||
get => (double)GetValue(CurrentReadingProperty);
|
||||
set => SetValue(CurrentReadingProperty, value);
|
||||
}
|
||||
|
||||
//<GetValueSourceScenario>
|
||||
// Coerce-value callback.
|
||||
private static object CoerceCurrentReading(DependencyObject depObj, object value)
|
||||
{
|
||||
// Get value source.
|
||||
ValueSource valueSource =
|
||||
DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty);
|
||||
|
||||
// Reject any property value change that's a locally set value.
|
||||
return valueSource.BaseValueSource == BaseValueSource.Local ?
|
||||
DependencyProperty.UnsetValue : value;
|
||||
}
|
||||
//</GetValueSourceScenario>
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace CodeSampleCsharp.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeSampleCsharp.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="CodeSampleCsharp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:CodeSampleCsharp"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="Application"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:CodeSampleVb"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
Class Application
|
||||
|
||||
' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
|
||||
' can be handled in this file.
|
||||
|
||||
End Class
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
Imports System.Windows
|
||||
|
||||
'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries can be found.
|
||||
'1st parameter: where theme specific resource dictionaries are located
|
||||
'(used if a resource is not found in the page,
|
||||
' or application resource dictionaries)
|
||||
|
||||
'2nd parameter: where the generic resource dictionary is located
|
||||
'(used if a resource is not found in the page,
|
||||
'app, and any theme specific resource dictionaries)
|
||||
<Assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<RootNamespace>CodeSampleVb</RootNamespace>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Import Include="System.Windows" />
|
||||
<Import Include="System.Windows.Controls" />
|
||||
<Import Include="System.Windows.Data" />
|
||||
<Import Include="System.Windows.Documents" />
|
||||
<Import Include="System.Windows.Input" />
|
||||
<Import Include="System.Windows.Media" />
|
||||
<Import Include="System.Windows.Media.Imaging" />
|
||||
<Import Include="System.Windows.Navigation" />
|
||||
<Import Include="System.Windows.Shapes" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<Window x:Class="CodeSampleVb.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Dependency property callbacks and validation" Height="200" Width="800" Loaded="RunTests">
|
||||
<Grid>
|
||||
<Label x:Name="lblMessage" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
Namespace CodeSampleVb
|
||||
|
||||
' <summary>
|
||||
' Interaction logic for MainWindow.xaml.
|
||||
' </summary>
|
||||
Partial Public Class MainWindow
|
||||
Inherits Window
|
||||
|
||||
Public Sub New()
|
||||
InitializeComponent()
|
||||
End Sub
|
||||
|
||||
Public Sub RunTests(sender As Object, e As RoutedEventArgs)
|
||||
TestValidationBehavior()
|
||||
TestCoercionBehavior()
|
||||
TestGetValueSource()
|
||||
End Sub
|
||||
|
||||
'<TestValueValidationScenario>
|
||||
Public Shared Sub TestValidationBehavior()
|
||||
Dim gauge As New Gauge1()
|
||||
|
||||
Debug.WriteLine($"Test value validation scenario:")
|
||||
|
||||
' Set allowed value.
|
||||
gauge.CurrentReading = 5
|
||||
Debug.WriteLine($"Current reading: {gauge.CurrentReading}")
|
||||
|
||||
Try
|
||||
' Set disallowed value.
|
||||
gauge.CurrentReading = Double.PositiveInfinity
|
||||
Catch e As ArgumentException
|
||||
Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}")
|
||||
End Try
|
||||
|
||||
Debug.WriteLine($"Current reading: {gauge.CurrentReading}")
|
||||
|
||||
' Current reading: 5
|
||||
' Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
|
||||
' Current reading 5
|
||||
End Sub
|
||||
'</TestValueValidationScenario>
|
||||
|
||||
'<TestCurrentMinMaxScenario>
|
||||
Public Shared Sub TestCoercionBehavior()
|
||||
|
||||
' Set initial values.
|
||||
Dim gauge As New Gauge2 With {
|
||||
.MinReading = 0,
|
||||
.MaxReading = 10,
|
||||
.CurrentReading = 5
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Test current/min/max values scenario:")
|
||||
|
||||
' Current reading is not coerced.
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
|
||||
|
||||
' Current reading is coerced to max value.
|
||||
gauge.MaxReading = 3
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
|
||||
|
||||
' Current reading is coerced, but tracking back to the desired value.
|
||||
gauge.MaxReading = 4
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
|
||||
|
||||
' Current reading reverts to the desired value.
|
||||
gauge.MaxReading = 10
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
|
||||
|
||||
' Current reading remains at the desired value.
|
||||
gauge.MinReading = 5
|
||||
gauge.MaxReading = 5
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
|
||||
|
||||
' Current reading: 5 (min=0, max=10)
|
||||
' Current reading: 3 (min=0, max=3)
|
||||
' Current reading: 4 (min=0, max=4)
|
||||
' Current reading: 5 (min=0, max=10)
|
||||
' Current reading: 5 (min=5, max=5)
|
||||
End Sub
|
||||
'</TestCurrentMinMaxScenario>
|
||||
|
||||
'<TestGetValueSourceScenario>
|
||||
Public Shared Sub TestGetValueSource()
|
||||
|
||||
Dim gauge As New Gauge3 With {
|
||||
.CurrentReading = 5
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Test GetValueSource scenario:")
|
||||
|
||||
' Locally set value.
|
||||
gauge.CurrentReading = 7
|
||||
|
||||
' Locally set value is rejected.
|
||||
Debug.WriteLine($"Current reading: " &
|
||||
$"{gauge.CurrentReading}")
|
||||
|
||||
' Current reading: 5
|
||||
End Sub
|
||||
'</TestGetValueSourceScenario>
|
||||
|
||||
End Class
|
||||
|
||||
'<ValueValidationScenario>
|
||||
Public Class Gauge1
|
||||
Inherits Control
|
||||
|
||||
Public Sub New()
|
||||
MyBase.New()
|
||||
End Sub
|
||||
|
||||
Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
|
||||
DependencyProperty.Register(
|
||||
name:="CurrentReading",
|
||||
propertyType:=GetType(Double),
|
||||
ownerType:=GetType(Gauge1),
|
||||
typeMetadata:=New FrameworkPropertyMetadata(
|
||||
defaultValue:=Double.NaN,
|
||||
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure),
|
||||
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
|
||||
|
||||
Public Property CurrentReading As Double
|
||||
Get
|
||||
Return GetValue(CurrentReadingProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(CurrentReadingProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
Public Shared Function IsValidReading(value As Object) As Boolean
|
||||
Dim val As Double = value
|
||||
Return Not val.Equals(Double.NegativeInfinity) AndAlso
|
||||
Not val.Equals(Double.PositiveInfinity)
|
||||
End Function
|
||||
|
||||
End Class
|
||||
'</ValueValidationScenario>
|
||||
|
||||
'<CurrentMinMaxScenario>
|
||||
Public Class Gauge2
|
||||
Inherits Control
|
||||
|
||||
Public Sub New()
|
||||
MyBase.New()
|
||||
End Sub
|
||||
|
||||
' Register a dependency property with the specified property name,
|
||||
' property type, owner type, property metadata, And callbacks.
|
||||
Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
|
||||
DependencyProperty.Register(
|
||||
name:="CurrentReading",
|
||||
propertyType:=GetType(Double),
|
||||
ownerType:=GetType(Gauge2),
|
||||
typeMetadata:=New FrameworkPropertyMetadata(
|
||||
defaultValue:=Double.NaN,
|
||||
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
|
||||
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceCurrentReading)),
|
||||
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
|
||||
|
||||
' CLR wrapper with get/set accessors.
|
||||
Public Property CurrentReading As Double
|
||||
Get
|
||||
Return GetValue(CurrentReadingProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(CurrentReadingProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
' Validate-value callback.
|
||||
Public Shared Function IsValidReading(value As Object) As Boolean
|
||||
Dim val As Double = value
|
||||
Return Not val.Equals(Double.NegativeInfinity) AndAlso Not val.Equals(Double.PositiveInfinity)
|
||||
End Function
|
||||
|
||||
' Property-changed callback.
|
||||
Private Shared Sub OnCurrentReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
depObj.CoerceValue(MinReadingProperty)
|
||||
depObj.CoerceValue(MaxReadingProperty)
|
||||
End Sub
|
||||
|
||||
' Coerce-value callback.
|
||||
Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
|
||||
Dim gauge As Gauge2 = CType(depObj, Gauge2)
|
||||
Dim currentVal As Double = value
|
||||
currentVal = If(currentVal < gauge.MinReading, gauge.MinReading, currentVal)
|
||||
currentVal = If(currentVal > gauge.MaxReading, gauge.MaxReading, currentVal)
|
||||
Return currentVal
|
||||
End Function
|
||||
|
||||
'<MinMaxReadingProperty>
|
||||
Public Shared ReadOnly MaxReadingProperty As DependencyProperty =
|
||||
DependencyProperty.Register(
|
||||
name:="MaxReading",
|
||||
propertyType:=GetType(Double),
|
||||
ownerType:=GetType(Gauge2),
|
||||
typeMetadata:=New FrameworkPropertyMetadata(
|
||||
defaultValue:=Double.NaN,
|
||||
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMaxReadingChanged),
|
||||
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMaxReading)),
|
||||
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
|
||||
|
||||
' CLR wrapper with get/set accessors.
|
||||
Public Property MaxReading As Double
|
||||
Get
|
||||
Return GetValue(MaxReadingProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(MaxReadingProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
' Property-changed callback.
|
||||
Private Shared Sub OnMaxReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
depObj.CoerceValue(MinReadingProperty)
|
||||
depObj.CoerceValue(CurrentReadingProperty)
|
||||
End Sub
|
||||
|
||||
' Coerce-value callback.
|
||||
Private Shared Function CoerceMaxReading(depObj As DependencyObject, value As Object) As Object
|
||||
Dim gauge As Gauge2 = CType(depObj, Gauge2)
|
||||
Dim maxVal As Double = value
|
||||
Return If(maxVal < gauge.MinReading, gauge.MinReading, maxVal)
|
||||
End Function
|
||||
|
||||
' Register a dependency property with the specified property name,
|
||||
' property type, owner type, property metadata, And callbacks.
|
||||
Public Shared ReadOnly MinReadingProperty As DependencyProperty =
|
||||
DependencyProperty.Register(
|
||||
name:="MinReading",
|
||||
propertyType:=GetType(Double),
|
||||
ownerType:=GetType(Gauge2),
|
||||
typeMetadata:=New FrameworkPropertyMetadata(
|
||||
defaultValue:=Double.NaN,
|
||||
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMinReadingChanged),
|
||||
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMinReading)),
|
||||
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
|
||||
|
||||
' CLR wrapper with get/set accessors.
|
||||
Public Property MinReading As Double
|
||||
Get
|
||||
Return GetValue(MinReadingProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(MinReadingProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
' Property-changed callback.
|
||||
Private Shared Sub OnMinReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
depObj.CoerceValue(MaxReadingProperty)
|
||||
depObj.CoerceValue(CurrentReadingProperty)
|
||||
End Sub
|
||||
|
||||
' Coerce-value callback.
|
||||
Private Shared Function CoerceMinReading(depObj As DependencyObject, value As Object) As Object
|
||||
Dim gauge As Gauge2 = CType(depObj, Gauge2)
|
||||
Dim minVal As Double = value
|
||||
Return If(minVal > gauge.MaxReading, gauge.MaxReading, minVal)
|
||||
End Function
|
||||
'</MinMaxReadingProperty>
|
||||
|
||||
End Class
|
||||
'</CurrentMinMaxScenario>
|
||||
|
||||
Public Class Gauge3
|
||||
Inherits Control
|
||||
|
||||
Public Sub New()
|
||||
MyBase.New()
|
||||
End Sub
|
||||
|
||||
' Register a dependency property with the specified property name,
|
||||
' property type, owner type, property metadata, And callbacks.
|
||||
Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
|
||||
DependencyProperty.Register(
|
||||
name:="CurrentReading",
|
||||
propertyType:=GetType(Double),
|
||||
ownerType:=GetType(Gauge3),
|
||||
typeMetadata:=New FrameworkPropertyMetadata(
|
||||
defaultValue:=Double.NaN,
|
||||
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
|
||||
propertyChangedCallback:=Nothing,
|
||||
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceCurrentReading)))
|
||||
|
||||
' CLR wrapper with get/set accessors.
|
||||
Public Property CurrentReading As Double
|
||||
Get
|
||||
Return GetValue(CurrentReadingProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(CurrentReadingProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
'<GetValueSourceScenario>
|
||||
' Coerce-value callback.
|
||||
Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
|
||||
' Get value source.
|
||||
Dim valueSource As ValueSource =
|
||||
DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty)
|
||||
|
||||
' Reject any property value that's a locally set value.
|
||||
Return If(valueSource.BaseValueSource = BaseValueSource.Local, DependencyProperty.UnsetValue, value)
|
||||
End Function
|
||||
'</GetValueSourceScenario>
|
||||
|
||||
End Class
|
||||
|
||||
End Namespace
|
||||
@@ -92,6 +92,8 @@ items:
|
||||
href: properties/custom-dependency-properties.md
|
||||
- name: Dependency property metadata
|
||||
href: properties/dependency-property-metadata.md
|
||||
- name: Dependency property callbacks and validation
|
||||
href: properties/dependency-property-callbacks-and-validation.md
|
||||
- name: Common tasks
|
||||
items:
|
||||
- name: Implement a dependency property
|
||||
|
||||
Reference in New Issue
Block a user