Getting Started with Data Binding in C# Xamarin

2019年12月15日 44点热度 0人点赞 0条评论
内容目录

C# Xamarin Data Binding Basics

[TOC]

About Data Binding

Xamarin One-way and Two-way Binding

Xaml Binding

C# Code Binding

Before diving deeper, here are some pseudocode snippets to help beginners like myself get started...

Suppose we have two controls: a Slider and a Label.

            Slider slider = new Slider()
            {
                Maximum = 1,
                Value = 10
            };

            Label label = new Label();
            label.Text = slider.Value.ToString();

The Slider's minimum sliding unit is 1, and its initialized value is 10.

We want the Label to display the Slider's value, which can be directly assigned in code.

            label.Text = slider.Value.ToString();

However, this only retrieves the value once. We want the Label to dynamically display the Slider's value each time it slides, which requires binding.

Method 1:

            Slider slider = new Slider()
            {
                Maximum = 1,
                Value = 10
            };

            Label label = new Label();
            label.Text = "666"; // Just initializing with a random value

            label.BindingContext = slider;      // Associate with an object

            // Set a binding
            // Bind the Label's Text property to the Slider's Value property
            label.SetBinding(Label.TextProperty, "Value");

Method 2:

            Slider slider = new Slider()
            {
                Maximum = 1,
                Value = 10
            };

            Label label = new Label();
            label.Text = "666"; // Just initializing with a random value

            Binding binding = new Binding()
            {
                Source = slider, // Bind to this data source
                Path = "Value"   // Bind to the property's attribute in the data source
            };

            // Bind
            label.SetBinding(Label.TextProperty, binding);

The keywords to remember are:

BindingContext(), SetBinding(), Binding, Source, Path.

View-View Binding

View-View Binding refers to the binding between UI controls, which can be accomplished using Xaml code without needing C# code.

In the previous section, we demonstrated two binding methods with pseudocode. Now, let's implement those binding examples in Xaml.

First, we need to establish the data source.

<Slider x:Name="slider" Maximum="1.0" VerticalOptions="CenterAndExpand" />

To bind data, we use {Binding ... ...}

Now we can implement the first binding method.

        <Label x:Name="label" 
               BindingContext="{x:Reference Name=slider}"    
               Text="{Binding Path=Value}" />

x:Reference is an extension markup that references an instance declared elsewhere in the XAML markup by its x:Name. It is a fixed format primarily filled with the Name of the data control's X:Name attribute.

{Binding Path=Value} indicates that the operation is a Binding, meaning that we are binding to the data, specifically to the Value property of the slider.

In this binding method, we first bind the data source object to the BindingContext property and then bind the Value property of the data source object to the Text property.

For the second method:

        <Label Text="{Binding Source={x:Reference Name=slider}, Path=Value}" />

In the second method, we directly use {Binding ... ...} to bind data, where the Source parameter specifies the data source object to bind, and the Path binds to a particular property of that object.

To make the layout more appealing, let's summarize the above code in a StackLayout:

    <StackLayout>
        <Label x:Name="label" 
               BindingContext="{x:Reference Name=slider}"    
               Text="{Binding Path=Value}" />
        
        <Slider x:Name="slider" Maximum="1.0" VerticalOptions="CenterAndExpand" />
        
        <Label Text="{Binding Source={x:Reference Name=slider}, Path=Value}" />

    </StackLayout>

However, the number of decimal places above is too many and does not match our desired format. We can format the data using StringFormat.

Text="{Binding Path=Value,StringFormat='{0:F1}'}"

Binding Modes

Binding Enumeration

The BindingMode enum for binding types:

  • Default
  • OneWay - value flows from source to target
  • OneWayToSource - value flows from target to source
  • TwoWay - value flows in both directions between source and target
  • OneTime - data flows from source to target, but only when the BindingContext changes

The previous data binding is one-to-one and unidirectional, meaning the Slider control exists first, followed by binding in the Label.

In practical scenarios, we need one-to-one with two-way influence, and one-to-many where multiple data sources aggregate into one control.

Different properties of a single control can also be bound. However, each control can only have one BindingContext, meaning that multiple data bindings on that view must all reference properties of the same object.

If you were to use the first method from the previous section, you could only bind to one object and utilize that object's properties.

Using the second method allows binding to multiple data sources.

One-to-Many - Target Binding Source Data

Based on the previous example, if a Label has multiple properties that need different data bindings, we could write it like this.

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <!-- Scale: Size, Rotation: Angle -->
        <Label x:Name="label"
               Text="TEXT"
               Scale="{Binding Source={x:Reference Name=slider1},Path=Value}"   
               Rotation="{Binding Source={x:Reference Name=slider2},Path=Value}"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Control Size -->
        <Slider x:Name="slider1"
                Grid.Row="1" Grid.Column="0"
                Value="5"
                Maximum="10" />

        <!-- Control Rotation Angle -->
        <Slider x:Name="slider2"
                Grid.Row="2" Grid.Column="0"
                Maximum="360" />
    </Grid>

One-to-Many - Source Object Binding Target

Using the method above can be inflexible. Suppose the Label is common, and we want to configure multiple properties' data sources for the Label. This would require us to implement bindings manually, and each property can only bind to one data object.

To reduce coupling and minimize the complexity of data binding in the Label, allowing multiple objects to modify the Label's properties, we can reverse it. Create multiple controls where the Label acts as a data source, while other controls are the target sources; the data is provided to the Label from these controls. It sounds convoluted, but we’ll clarify with an example.

        <!-- Scale: Size, Rotation: Angle -->
        <Label x:Name="label"
               Text="TEXT"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Control Size -->
        <Slider x:Name="scaleSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="1" Grid.Column="0"
                Maximum="10"
                Value="{Binding Scale, Mode=TwoWay}" />

        <!-- Control Rotation Angle -->
        <Slider x:Name="rotationSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="2" Grid.Column="0"
                Maximum="360"
                Value="{Binding Rotation, Mode=OneWayToSource}" />

In this scenario, the label doesn't handle anything. The scaleSlider and rotationSlider bind to the label as their data source. According to the binding definition, the label is the data source, and the label's property data acts as the property values for the target controls scaleSlider and rotationSlider.

Wait, it seems we've got it wrong. We want to use other controls to modify the label's properties, but now it appears we're using the label's property values as those of the controls.

The reason lies in the Mode.

The OneWayToSource enumeration means values flow from the target to the source.

From the perspective of binding and definition, the label is the data source, and the sliders are the targets, but the data flows reversely.

Two-way Binding in Text Box

The sample code is as follows:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="2*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
        <Editor x:Name="edit1"
                Grid.Row="0"
                Grid.Column="1"
                Grid.ColumnSpan="2"
                Text="a"/>
        
        <Editor x:Name="edit2"
                Grid.Row="1"
                Grid.Column="1"
                Grid.ColumnSpan="2"
                Text="{Binding Source={x:Reference edit1},Path=Text,Mode=TwoWay}"/>
    </Grid>

Official Example

The Microsoft official documentation has a more extensive example code, which you can refer to if interested:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderTransformsPage"
             Padding="5"
             Title="Slider Transforms Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <!-- Scaled and rotated Label -->
        <Label x:Name="label"
               Text="TEXT"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Slider and identifying Label for Scale -->
        <Slider x:Name="scaleSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="1" Grid.Column="0"
                Maximum="10"
                Value="{Binding Scale, Mode=TwoWay}" />

        <Label BindingContext="{x:Reference scaleSlider}"
               Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
               Grid.Row="1" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for Rotation -->
        <Slider x:Name="rotationSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="2" Grid.Column="0"
                Maximum="360"
                Value="{Binding Rotation, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationSlider}"
               Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
               Grid.Row="2" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationX -->
        <Slider x:Name="rotationXSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="3" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationX, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationXSlider}"
               Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
               Grid.Row="3" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationY -->
        <Slider x:Name="rotationYSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="4" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationY, Mode=OneWayToSource}" />

        <Label BindingContext="{x:Reference rotationYSlider}"
               Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
               Grid.Row="4" Grid.Column="1"
               VerticalTextAlignment="Center" />
    </Grid>
</ContentPage>

Simple Collection Binding

In MainPage.xaml, add:

    <ListView x:Name="lview">
    </ListView>

In MainPage.xaml.cs, change to:

    public partial class MainPage : ContentPage
    {
        public static List<string> lists = new List<string> {"a","b","c","d","e","f" };
        public MainPage()
        {
            InitializeComponent();
            lview.ItemsSource = lists;
        }
    }

After running, the list will automatically appear.

More detailed information about ListView will be covered in upcoming articles.

痴者工良

高级程序员劝退师

文章评论