C# WPF Dynamic Binding to External Objects


You are writing C# code in Visual Studio and you are designing an Options window for your application where users will be able to make some choices that they wish to save for the next time they run the application. These options are not sensitive, so saving them to an xml file will work fine.

To illustrate how this can be done, the code is presented below. It is simplified so that the only user input is a small amount of text.

To present this program I will show five figures: a screen shot of the running application, the xml file, the XAML file, the class file (used to hold the user’s text) and the code-behind file. The name of the executable file is DynBindExternObj1.exe.

DynamicBinding

The program, DynBindExternObj1.exe is shown above. The xml file, UserInputs.xml is shown below.

<?xml version="1.0"?>
<UserInputs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TextInput>here is the text I entered</TextInput>
</UserInputs>

In Visual Studio I have used XAML and the Designer to develop the window. There is only one window in this program. The XAML is shown below.

<Window x:Class="DynBindExternObj1.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:DynBindExternObj1"
        mc:Ignorable="d"
        Title="Dynamic Bindings" Height="350" Width="525" MinHeight="350" MinWidth="325" ResizeMode="CanResizeWithGrip">
    <Grid>
        <Grid.RowDefinitions>
            <!--Only 2 rows are used-->
            <RowDefinition />
            <RowDefinition Height="42"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Margin="0,-5,0,5">
            <Label x:Name="label" Content="DynBindExternObj1 - Solution in Visual Studio 2015"/>
            <TextBlock x:Name="textBlock" TextWrapping="Wrap" 
                       Text="Please enter some text below. Your text will persist even after application is closed because your text will be saved to an xml file called UserInputs.xml. When you run the application again you will see the text you entered from the last time. If you don't want to save your changes to the text, click the Canel button, otherwise click the Save button to save your text."/>
            <Label x:Name="label1" Content="Enter some text:" HorizontalAlignment="Left" Height="27" Width="103" Margin="50,0,0,0"/>
            <TextBox x:Name="textBox" Text="{Binding TextInput}" HorizontalAlignment="Left" Height="31" Margin="50,0,0,0" TextWrapping="Wrap" Width="336"/>
        </StackPanel>
        <Canvas Grid.Row="1">
            <Button x:Name="cancelButton" Content="_Cancel" Canvas.Right="12" Canvas.Top="10" Width="75" Click="cancelButton_Click"/>
            <Button x:Name="okButton" Content="_OK" Canvas.Right="110" Canvas.Top="10" Width="75" Click="okButton_Click"/>
        </Canvas>
    </Grid>
</Window>

The important things to take note of in the above XAML code is the Binding in the text box and the Click attribute in the two buttons.

I created a class called UserInputs to hold the data that the user types in. It is shown below. This class could be called anything you want and for example you may call it ProgramOptions if you are storing the user’s options.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;  // add this
using System.ComponentModel;  //  add this

namespace DynBindExternObj1
{
    [Serializable]
    public class UserInputs : INotifyPropertyChanged
    {
        private string _textInput = "please enter text for me to store";
        public string TextInput
        {
            get { return _textInput; }
            set
            {
                _textInput = value;
                OnPropertyChanged(nameof(TextInput));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The last part is the main window’s code. The file is MainWindow.xaml.cs. It is shown below. The key line to take note of understand is the line with DataContext. The DataContext control defines a data source that can be used for data binding on all child elements of an element. You will often have a single instance of a class that holds most of the data that is used in a view. If this is the case you can set the DataContent of the window to the instance of that object, which makes you able to bind properties from that class to your view.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;                 //  add this
using System.Xml.Serialization;  // add this

namespace DynBindExternObj1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private UserInputs _userInput;
        
        public MainWindow()
        {
            // page 447 step 4
            if (_userInput == null)
            {
                if (File.Exists("UserInputs.xml"))
                {
                    using (var stream = File.OpenRead("UserInputs.xml"))
                    {
                        var serializer = new XmlSerializer(typeof(UserInputs));
                        _userInput = serializer.Deserialize(stream) as UserInputs;
                    }
                }
                else
                    _userInput = new UserInputs();
             }
            // 
            DataContext = _userInput;
            InitializeComponent();
        }
        private void okButton_Click(object sender, RoutedEventArgs e)
        {
            using (var stream = File.Open("UserInputs.xml", FileMode.Create))
            {
                var serializer = new XmlSerializer(typeof(UserInputs));
                serializer.Serialize(stream, _userInput);
            }
            Close();
        }
        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            _userInput = null;
            Close();
        }
    }
}

In the XAML file I have set the binding of the text box as follows:

<TextBox x:Name="textBox" Text="{Binding TextInput}" ...

Setting the DataContext of the window to an instance of UserInputs allows you to bind to this class instance simply by specifying the class property (TextInput) to use in the binding. In this example I use a text box. This will work for other controls as well.

The class implements the INotifyPropertyChanged interface which means the class is now able to inform WPF that a property has changed.

Leave a comment

Your email address will not be published. Required fields are marked *