Home > front end >  Trouble understanding DataBindings in WPF
Trouble understanding DataBindings in WPF

Time:07-10

I'm an absolute beginner in WPF and tried to setup a simple DataBinding that updates the text of a TextBlock based on the text value in a TextBox when you click a button. I got it to work, but i found two different variants of doing it and in both cases something seems off.

Variant 01 works just as it should, but the fact that the target updates the source seems off. In Variant 02 the source updates the target, but the UpdateSourceTrigger is useless and the only thing the mode does is blocking the target from getting updated, so i can do it manually.

Both variants get the thing done, but in both cases there is something that bothers me and seems off. So what's the 'right' way of doing this and DataBindings in general?

C# Code for both variants:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestProject {
    public partial class MainWindow {
        public MainWindow() {
            InitializeComponent();

            // Value propagation: Target(InputFieldVariant01) -> Source(OutputFieldVariant01) 
            Binding binding = new Binding("Text");
            binding.Source = OutputFieldVariant01;
            binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            binding.Mode = BindingMode.OneWayToSource;
            InputFieldVariant01.SetBinding(TextBox.TextProperty, binding);

            // Value propagation: Source(InputFieldVariant02) -> Target(OutputFieldVariant02)
            Binding binding2 = new Binding("Text");
            binding2.Source = InputFieldVariant02;
            binding2.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            binding2.Mode = BindingMode.OneWayToSource; // blocks the updating of the OutputField i guess (?)
            OutputFieldVariant02.SetBinding(TextBlock.TextProperty, binding2);
        }

        private void refreshBtnVariant01_refreshTextBlock(object sender, RoutedEventArgs e) {
            InputFieldVariant01.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
        }

        private void refreshBtnVariant02_refreshTextBlock(object sender, RoutedEventArgs e) {
            OutputFieldVariant02.GetBindingExpression(TextBlock.TextProperty)?.UpdateTarget();
        }
    }
}

and here is my .xaml:

<Window x:Class="TestProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestProject"
        mc:Ignorable="d"
        Title="MainWindow" Height="120" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        
        <TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Center">Variant 01</TextBlock>
        <TextBox Grid.Row="2" Grid.Column="2" Name="InputFieldVariant01" MinWidth="100"></TextBox>
        <Button Grid.Row="3" Grid.Column="2" Content="Refresh TextBlock" Click="refreshBtnVariant01_refreshTextBlock" Margin="0, 5"></Button>
        <TextBlock Grid.Row="4" Grid.Column="2" Name="OutputFieldVariant01"></TextBlock>
        
        <TextBlock Grid.Row="1" Grid.Column="4" TextAlignment="Center">Variant 02</TextBlock>
        <TextBox Grid.Row="2" Grid.Column="4" Name="InputFieldVariant02" MinWidth="100"></TextBox>
        <Button Grid.Row="3" Grid.Column="4" Content="Refresh TextBlock" Click="refreshBtnVariant02_refreshTextBlock" Margin="0, 5"></Button>
        <TextBlock Grid.Row="4" Grid.Column="4" Name="OutputFieldVariant02"></TextBlock>
        
    </Grid>
</Window>

CodePudding user response:

A few points, you are doing all this binding stuff in code. I suggest looking into MVVM patterns since you are new. The basic premise is

M = Model - where the underlying data is coming from / stored to
V = View - the user interface presentation context
VM = ViewModel - the glue getting/sending data to ex: sql database, but also making available to the view / end user.

It can typically be found that you create a view model object such as a class and it has public getter/setter properties on it. When the object is created within your view constructor and set as the DataContext, all bindings can be done directly within the xaml. The class does not need to know what the view does with it, the view doesnt need to know how the data is available. So you can simplify much of this such as

namespace TestProject 
{
    public partial class MainWindow 
    {
        public MainWindow() 
        {
            InitializeComponent();
            DataContext = new MyViewModel();
        }
    }
}

Now, in another class such as example indicates above

namespace TestProject 
{
    public class MyViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        #endregion INotifyPropertyChanged Members        

        private string _someString = "test string";
        public string SomeString 
        {
            get { return _someString; }
            set
            {
                _someString = value;
                RaisePropertyChanged(nameof(SomeString));
            }
        }

        private int _someNumber = 18;
        public int SomeNumber 
        {
            get { return _someNumber; }
            set
            {
                _someNumber = value;
                RaisePropertyChanged(nameof(SomeNumber));
            }
        }

        public List<SomeTableStructureFromDatabase> ListOfData { get; }

        public MyViewModel()
        {
           ListOfData = SomeMethodToGetDataFromSQLDatabase();
        }
    }
}

So, in the above sample, you can see the PUBLIC get/set with some default values to the string and numeric values. Yes, I also included the INotifyPropertyChanged extension to the class. You can read more on that later, but allows view components to trigger refresh when things change either internally to the class to refresh the view, or being pushed from the view back to the view model.

Now, how to handle the view. As YOU are developing, you just need to know the names of the pieces on the view model that are exposed publicly. Then, in the xaml, identify the bindings directly. Again, the view should not know how or where the data parts are coming from, just what they are called and exposed as. So the VIEW (MainWindow.xaml) might have your entry textblocks/textbox, etc as:

<TextBox MinWidth="100" [yes, you can assign grid row/columns as normal]
    Text="{Binding SomeString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />

<TextBox Width="45" MaxLength="4"
    Text="{Binding SomeNumber, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
        

Notice the direct binding of the textboxes. Since the MainWindow has its DataContext based on the MyViewModel class created in its constructor, the publicly exposed properties are available to bind to. It can all be handled in the xaml. You just need to know what the property is called on the class. You dont need to explicitly NAME each of the controls so the .CS code has to know the view side name as well.

Also, your context of forcing the mode as ONE WAY TO SOURCE is not going to be as common as you might think. Think of it this way. You want to pull data from a database, expose it for being edited and then saved back. If one-way to the source, you cant get and push TO the field to show the user. So, explicitly stating the mode is one way you might shy away from unless truly needed.

Anyhow, hope this might open your mindset to additional reading and understanding of some bindings. Lastly, as I sampled the class with default text string and numeric values, when you do run the form, you should see those values default when the form is presented. Also, as a numeric (or even date/time as applicable to DatePicker control), you dont have to worry about data conversion from text entry and making sure numeric or bogus text. The binding will only update the value if it is the proper data type for the property being bound to.

CodePudding user response:

The way I was taught binding is by implementing the INotifyPropertyChanged interface (See docs @MSDN). For collections there is an ObservableCollection Type. You may want to look at MVVM pattern later on but for now just focus on Binding. In XAML there is a special syntax called XAML Markup Extension. Here is an example @MSDN.

  • Related