WPF Binding Foundation
WPF Class Inheritance Diagram
Object class: in Root type of all types in Net
DispatcherObject class: Most objects in WPF derive from DispatcherObject, which provides basic constructs for handling concurrency and threads. WPF is based on the message system implemented by the scheduler.
DependencyObject class: represents an object participating in the dependency property system.
Visual class: provides support for rendering in WPF, including hit test, coordinate conversion and bounding box calculation.
UIElement class: the base class for WPF core level implementation, which is based on Windows Presentation Foundation (WPF) elements and basic presentation features.
FrameworkElement class: provides WPF framework level property set, event set and method set for Windows Presentation Foundation (WPF) elements. This class represents the attached WPF framework level implementation, which is built on the WPF core level API defined by UIElement.
Control class: represents the base class of user interface (UI) elements. These elements use ControlTemplate to define their appearance.
ContentControl class: refers to the control containing single item content.
ItemsControl class: represents a control that can be used to render a collection of items.
Decorator class: provides the base class of elements that apply effects on or around a single child element (such as Border or Viewbox).
Image class: refers to the control that displays images.
MediaElement class: represents the control containing audio and/or video.
Panel class: provides a base class for all Panel elements. Use the Panel element to place and arrange sub objects in Windows Presentation Foundation (WPF) applications.
Sharp class: provides a base class for shape elements such as Ellipse, Polygon, and Rectangle.
Four components of binding
<TextBox Text="{Binding ElementName=textBox2, Path=Text, Mode=Default, UpdateSourceTrigger=PropertyChanged}">Text</Text>
textBox.SetBinding(TextBox. TextProperty, new Binding() { //Both Source and ElementName are specified binding sources //Path = new PropertyPath("TextStr"), //Source = data, Path = new PropertyPath("Text"), ElementName = "textBox2", Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
Data binding direction, 5 binding modes
Trigger conditions for data binding
The default of the binding direction and trigger condition is in the dependency attribute metadata. We can read the binding direction and metadata by obtaining the dependency attribute metadata
var meta = TextBox.TextProperty.GetMetadata(textBox) as FrameworkPropertyMetadata; Console.WriteLine($"update source trigger is {meta.DefaultUpdateSourceTrigger}, binding two way is default {meta. BindsTwoWayByDefault}");
Printed results
update source trigger is LostFocus, binding two way is default True
Four ways to bind source
|
|
---|---|
|
|
|
|
|
|
|
|
Binding data change notification
Actually you are encountering a another hidden aspect of WPF, that's it WPF's data binding engine will data bind to PropertyDescriptor instance which wraps the source property if the source object is a plain CLR object and doesn't implement INotifyPropertyChanged interface. And the data binding engine will try to subscribe to the property changed event through PropertyDescriptor.AddValueChanged() method. And when the target data bound element change the property values, data binding engine will call PropertyDescriptor.SetValue() method to transfer the changed value back to the source property, and it will simultaneously raise ValueChanged event to notify other subscribers (in this instance, the other subscribers will be the TextBlocks within the ListBox.
In fact, you have encountered another hidden aspect of WPF, that is, WPF's data binding engine binds data to the PropertyDescriptor instance. If the source object is a common CLR object and does not implement the INotifyPropertyChanged interface, the PropertyDescriptor instance will wrap the source property. The data binding engine will attempt to pass the PropertyDescriptor The AddValueChanged() method subscribes to the property change event. When the target data binding element changes the property value, the data binding engine will call PropertyDescriptor The SetValue() method transfers the changed value back to the source property, and it also raises a ValueChanged event to notify other subscribers.
public class Data4Binding { private string _textStr = "Hello Binding"; public string TextStr { get { return _textStr; } set { _textStr = value; } } } ... Data4Binding data = new Data4Binding(); textBox.SetBinding(TextBox. TextProperty, new Binding() { Path = new PropertyPath("TextStr"), Source = data, Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); textBox2.SetBinding(TextBox. TextProperty, new Binding() { Path = new PropertyPath("TextStr"), Source = data, Mode = BindingMode.OneWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
public class Data4Binding: INotifyPropertyChanged { private string _textStr = "Hello Binding"; public string TextStr { get => _textStr; set { if (value == _textStr) return; _textStr = value; OnPropertyChanged(nameof(TextStr)); } } private void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler? PropertyChanged; }
Binding Data Converters
Used for conversion of values between different data types
public class ExerciseConverter : IValueConverter { //Bind Source Attribute ->Bind Target Attribute Conversion public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } //Bind target attribute ->Bind source attribute conversion public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
Binding data validation
Most applications that accept user input need to have validation logic to ensure that the user input meets the requirements; Validation checks can be based on type, scope, format, or other specific requirements.
Validation rules
Custom validation rules
public class ExerciseValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { If (string. IsNullOrWhiteSpace (value. ToString())) return new ValidationResult (false, "cannot be empty"); if (Regex. IsMatch(value. ToString(), @"^(\-)?\d+(\.\d+)?$")) { return ValidationResult.ValidResult; } else { Return new ValidationResult (false, "Only numbers can be entered"); } } }
Verification method 1
private string _errorMessage; public string ErrorMessage { get { return _errorMessage; } set { if (value == _errorMessage) return; _errorMessage = value; OnPropertyChanged(nameof(ErrorMessage)); } }
<Window. Resources> <local:Data4Binding x:Key="data4Binding"/> </Window. Resources> <Window. DataContext> <StaticResource ResourceKey="data4Binding"/> </Window. DataContext> <DockPanel> <TextBlock DockPanel. Dock="Right" Text="{Binding ErrorMessage}" Foreground="Red" VerticalAlignment="Center"/> <TextBox x:Name="textBoxWithValidation" MinWidth="120" Margin="5" Validation.Error="textBox_Error" VerticalAlignment="Center"> <TextBox. Text> <Binding Path="TextStr" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True"> <Binding. ValidationRules> <local:ExerciseValidationRule ValidatesOnTargetUpdated="True"/> </Binding. ValidationRules> </Binding> </TextBox. Text> </TextBox> </DockPanel>
private void textBox_Error(object sender, ValidationErrorEventArgs e) { if (Validation. GetErrors(textBoxWithValidation). Count > 0) { var data = DataContext as Data4Binding; data.ErrorMessage = Validation.GetErrors(textBoxWithValidation)[0]. ErrorContent.ToString(); } else { var data = DataContext as Data4Binding; data.ErrorMessage = ""; } }
public string ErrorMessage { get { return (string)GetValue(ErrorMessageProperty); } set { SetValue(ErrorMessageProperty, value); } } // Using a DependencyProperty as the backing store for ErrorMessage. This enables animation, styling, binding, etc... public static readonly DependencyProperty ErrorMessageProperty = DependencyProperty.Register("ErrorMessage", typeof(string), typeof(MainWindow), new PropertyMetadata(""));
Verification method 2
<Application. Resources> <ResourceDictionary> <ResourceDictionary. MergedDictionaries> <ResourceDictionary Source="/ExerciseBinding;component/ValidationContent.xaml" /> </ResourceDictionary. MergedDictionaries> <ControlTemplate x:Key="ErrorTemplate"> <AdornedElementPlaceholder> <local:ValidationContent/> </AdornedElementPlaceholder> </ControlTemplate> </ResourceDictionary> </Application. Resources>
<ResourceDictionary xmlns=" http://schemas.microsoft.com/winfx/2006/xaml/presentation " xmlns:x=" http://schemas.microsoft.com/winfx/2006/xaml " xmlns:local="clr-namespace:ExerciseBinding"> <ControlTemplate x:Key="ValidationToolTipTemplate"> <Border x:Name="Root" Margin="5,0,0,0" Opacity="0" Padding="0,0,20,20" RenderTransformOrigin="0,0"> <Border. RenderTransform> <TranslateTransform x:Name="xform" X="-25" /> </Border. RenderTransform> <VisualStateManager. VisualStateGroups> <VisualStateGroup x:Name="OpenStates"> <VisualStateGroup. Transitions> <VisualTransition GeneratedDuration="0" /> <VisualTransition GeneratedDuration="0:0:0.2" To="Open"> <Storyboard> <DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform"> <DoubleAnimation. EasingFunction> <BackEase Amplitude=".3" EasingMode="EaseOut" /> </DoubleAnimation. EasingFunction> </DoubleAnimation> <DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" /> </Storyboard> </VisualTransition> </VisualStateGroup. Transitions> <VisualState x:Name="Closed"> <Storyboard> <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" /> </Storyboard> </VisualState> <VisualState x:Name="Open"> <Storyboard> <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform" /> <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager. VisualStateGroups> <FrameworkElement. Effect> <DropShadowEffect BlurRadius="11" ShadowDepth="6" Opacity="0.4" /> </FrameworkElement. Effect> <Border Background="#FFDC000C" BorderThickness="1" BorderBrush="#FFBC000C"> <TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding [0].ErrorContent}" UseLayoutRounding="false" /> </Border> </Border> </ControlTemplate> <Style TargetType="local:ValidationContent"> <Setter Property="Template"> <Setter. Value> <ControlTemplate> <Border BorderBrush="#FFDB000C" BorderThickness="1" x:Name="root"> <ToolTipService. ToolTip> <ToolTip x:Name="validationTooltip" Placement="Left" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}" /> </ToolTipService. ToolTip> <Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Width="12" Margin="1,-4,-4,0" VerticalAlignment="Top"> <Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0" /> <Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0" /> </Grid> </Border> <ControlTemplate. Triggers> <MultiDataTrigger> <MultiDataTrigger. Conditions> <Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type AdornedElementPlaceholder}}, Path= AdornedElement.IsKeyboardFocusWithin, Mode=OneWay}" Value="True" /> <Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type AdornedElementPlaceholder}}, Path= AdornedElement.(Validation. HasError), Mode=OneWay}" Value="True" /> </MultiDataTrigger. Conditions> <Setter TargetName="validationTooltip" Property="IsOpen" Value="True" /> </MultiDataTrigger> </ControlTemplate. Triggers> </ControlTemplate> </Setter. Value> </Setter> </Style> </ResourceDictionary>