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

1. Bindingsource binding source can be omitted. If omitted, the DataContext will be searched from bottom to top. Its value can be specified by the Source or ElementName property, either of which can be selected

2. Bindingsource.path Binds the source attribute (the path of the source attribute) Path=bindingsource.path can omit Path=, unless the order changes, Path needs to be added=

3. Bindingtarget

4. Bindingtarget. property [denpendencyProperty] Bind target property, Must be DenpendencyProperty

Viewing in xaml may not be so intuitive

 <TextBox Text="{Binding ElementName=textBox2, Path=Text, Mode=Default, UpdateSourceTrigger=PropertyChanged}">Text</Text>

Bind in code, where the binding target is textBox and the binding target property is TextProperty

 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

1. The value of OneWay binding source property can be passed to the value of binding target property

2. The value of TwoWay binding source attribute can be passed to the value of binding target attribute, and the value of binding target attribute can also be passed to the value of binding source attribute

3. The value of OneWayToSource binding target property can be passed to the value of binding meta source property

4. The property value of OneTime binding source will be transferred to the target property value during initialization, and will not be transferred later

5. Default If the bindingsource.path attribute is private set, it is equivalent to OneWay; if it is public set, it is equivalent to TwoWay

Trigger conditions for data binding

The binding modes involved: TwoWay, OneWayToSource, Default (when the property set is public)

The updatesourceTrigger determines the triggering conditions

1. LostFocus passes the value to the source property value when the control loses focus

2. PropertyChanged Every time the property value of the control changes, the value is immediately transferred to the source property value

3. Explicit users can only transfer the value to the property value by calling the UpdateSource method

4. Default Binds the default UpdateSourceTrigger value of the target property. The default value of most dependency properties is PropertyChanged, while the default value of the Text property is LostFocus; The programming method to determine the default UpdateSourceTrigger value of the dependency property is to use GetMetadata to obtain the property metadata of the property, and then check the value of the DefaultUpdateSourceTrigger property

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

1、Source

2、ElementName

3、RelativeSource

Interpretation of the RelativeSourceMode enumeration value

name explain
Self Reference the element on which the binding is set, and allow one attribute of the element to be bound to other attributes of the same element
FindAncestor Reference the parent of a data binding element. You can use it to bind to the parent of a specific type or its subclass. If you want to specify AncestorType and AncestorLevel, you can use this mode
PreviousData Allow binding of previous data items in the displayed data item list
TemplatedParent Reference the element to which the template is applied, which is similar to setting the TemplateBindingExtension, and is only applicable when the Binding is in the template

4、DataContext

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.

Before describing INotifyPropertyChanged, look at the above content. Ordinary CLR type properties can complete two-way binding of data even if they do not implement INotifyPropertyChanged. This is the property callback method of ValueChanged and SetValue implicitly completed by WPF's binding engine.

 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 });

Where textBox is a two-way binding and textBox2 is a single binding of OneWay, that is, the update of the binding source property and the root default or set trigger condition update the value of the TextBox 2's Text property. It is easy to see that Data4Binding does not implement INotifyPropertyChanged, but can also perform two-way binding.

The following INotifyPropertyChanged interface is implemented for Data4Binding

 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

The ValidationRule object can check whether the property value is valid. WPF has two built-in ValidationRule objects: ExceptionValidationRule DataErrorValidationRule

ExceptionValidationRule checks the exception thrown when updating the binding source property. It is used to show that the setting of ExceptionValidationRule is to set ValidatesOnExceptions on the binding object to true

The DataErrorValidationRule checks for errors caused by the implementation of the IDataErrorInfo interface object. It is used to display that the DataErrorValidationRule is set to set ValidatesOnDataErrors on the binding object to true

Custom validation rules

Make a custom verification that can only enter numbers

 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

Add the ErrorMessage property used for binding in the binding data source

 private string _errorMessage; public string ErrorMessage { get { return _errorMessage; } set { if (value == _errorMessage) return; _errorMessage = value; OnPropertyChanged(nameof(ErrorMessage)); } }

Add a TextBox using ValidationRules and a TextBlock for binding error display in Xaml

 <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>

Where textBox_Error's callback function

 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 = ""; } }

Of course, you can also directly add dependency attributes to the current xaml background code. This situation depends on the actual development situation

 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

Recommended methods

It is the Validation of TextBox ErrorTemplate Specifies the ControlTemplate

The content of this part needs to be understood more deeply about Xaml without detailed explanation. The introduction of resources in App. xaml

 <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>

Create a new independent file, ValidationContent.xaml

 <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>
posted @ 2022-10-31 17:33   illegal keyword   Reading( two hundred and fifty-four Comments( zero edit   Collection   report