the_cat21
the_cat21

Reputation: 1

WPF bind Textbox to a DataSet in another class

WPF is not normally my area, so I am a bit of a newbie, and I am having a bit of trouble figuring out how to achieve something in WPF which was a piece of cake in WinForms. I can't seem to find either the right thread in this forum or the right YouTube tutorial that leads me towards the answer. I am having problems getting a simple DataBinding to a WPF TextBox working correctly. The behaviour that I am trying to achieve is that any changes made to the TextBox are immediately reflected in the source class DataSet. It's a simple display/edit scenario and I'm sure there is a very simple answer.

This is how I would have done it in WinForms....

Form code:

public partial class Form1 : Form
{
    private DATARECORD CURRENTUSER;

    public Form1()
    {
        InitializeComponent();
        CURRENTUSER = new DATARECORD(@"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf");
        CURRENTUSER.FncBind(CtlCopiesToKeep, "Value", "tblUser.CopiesToKeep");
    }

    //Test code to display the value in the DataSet
    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(CURRENTUSER.copiesToKeep.ToString());
    }
}

Class code:

public class DATARECORD
{
    private string ConnectionString;
    private DataSet CurrentRecord;
    public int copiesToKeep { get { return Int32.Parse(CurrentRecord.Tables["tblUser"].Rows[0]["CopiesToKeep"].ToString()); } }

    public DATARECORD(string connectionString)
    {
        ConnectionString = connectionString;
        CurrentRecord = new DataSet();
        SQL SQL = new SQL(2);
        DataTable userTable = SQL.fncSelectAsTable(ConnectionString, "tblUser", "USERID=2");
        userTable.TableName = "tblUser";
        CurrentRecord.Tables.Add(userTable);
        userTable.Dispose();
    }

    public void FncBind(Control c, string type, string field)
    {
        c.DataBindings.Add(type, CurrentRecord, field, true, DataSourceUpdateMode.OnPropertyChanged);
    }
}

I then just have simple TextBox on the main Form called "CtlCopiesToKeep" and a "test" button.

Does anyone know of a nice, simple, example that can show how to do this?

Many thanks in advance, Dave

EDIT:

Hello Noel. Many thanks for taking the time to explain all that. I have put it altogether, but something seems to be wrong with the binding, because when I change the value in the TextBox it does not update the DataSet. Here is the code and the XAML. If anyone can point me in the right direction then it would be much appreciated.

UPDATED Main code

public partial class MainWindow : Window
{
    public DATARECORD SELECTEDUSER;
    private string ConnectionString = @"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf";

    public MainWindow()
    {
        InitializeComponent();
        SELECTEDUSER = new DATARECORD(ConnectionString);
        GrdMain.DataContext = SELECTEDUSER; 
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        SELECTEDUSER.fncShowVals("BasePath");
    }
}

UPDATED Class code

public class DATARECORD : INotifyPropertyChanged
{
    private string ConnectionString;
    private DataSet currentRecord = new DataSet();
    private string BasePath = null;

    public string basePath
    {
        get
        {
            return currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString() ;
        }
        set
        {
            BasePath = value;
            OnPropertyChanged("BasePath");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public DATARECORD(string connectionString)
    {
        ConnectionString = connectionString;
        SQL SQL = new SQL(ConnectionString, SQLVersion.CE);
        DataTable storageTable = SQL.fncSelectAsTable(ConnectionString, "tblStorage", "USERID=2");
        storageTable.TableName = "tblStorage";
        currentRecord.Tables.Add(storageTable);
        storageTable.Dispose();  
    }

    public void fncShowVals(string test)
    {
        MessageBox.Show(currentRecord.Tables["tblStorage"].Rows[0][test].ToString());
    }

    protected void OnPropertyChanged(string value)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(value));
        }
    }

}

XAML for TextBox

<Window x:Class="WpfBind.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid Name="GrdMain">
    <TextBox Text="{Binding basePath, Mode=TwoWay, UpdateSourceTrigger =PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="124,70,0,0" Name="CtlBaseFolder" VerticalAlignment="Top" Width="120" />
    <Label Content="BaseFolder" Height="28" HorizontalAlignment="Left" Margin="41,69,0,0" Name="label2" VerticalAlignment="Top" />
    <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="263,142,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>

UPDATE 02/04/2015

I now have this, but I don't understand how it references the DataSet? This code produces a blank textbox and if the value is changed it doesn't update the DataSet:

`private string ___basePath = null;

    protected string _basePath
    {
        get
        {
            return ___basePath;
        }
        set
        {
            ___basePath = value;
            OnPropertyChanged("basePath");
        }
    }

    public string basePath
    { //<- Bind to this property
        get
        {
            return ___basePath;
        }

        set
        {
            _basePath = value;
        }
    }`

The underlying DataSet value is stored here:

currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();

Many thanks in advance, Dave.

UPDATE - 02/04/2015 - 2

Hello Noel, I have applied your code, but it's still not working unfortunately (the DataSet does not reflect the changes in the TextBox if I click on the "test" button). Here is the whole code. I massively appreciate your time on this by the way, thanks so much!

    public partial class MainWindow : Window
{
    private string ConnectionString = @"Data Source=C:\Users\rr187718\Documents\Personal\Programming\DynamicBackup\DynamicBackup\bin\Debug\Data\dbData.sdf";
    private readonly DATARECORD _data = null;
    public DATARECORD Data
    {
        get
        {
            return _data;
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        _data = new DATARECORD(ConnectionString);
        DataContext = Data; //All controls connected to this class will now look for their value in 'Data' (DataContext inherits and must be a property because you can only bind to properties)

    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Data.fncShowVals("BasePath");
    }
}

public class DATARECORD : INotifyPropertyChanged
{
    private string ConnectionString;
    private DataSet currentRecord = new DataSet();

    private string ___basePath = null;
    private string _basePath
    {
        get
        {
            if (___basePath == null)
            {
                //We only access the currentRecord if we did not yet stored the value
                //   otherwise it would read the currentRecord every time you type a char 
                //   in the textbox.
                //   Also: Pay attention to multiple possible NullReferenceExceptions and IndexOutOfBoundsExceptions
                ___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
            }

            return (___basePath == String.Empty) ? null : ___basePath;
        }
        set
        {
            ___basePath = (value == null) ? String.Empty : value;
            NotifyPropertyChanged("BasePath");
        }
    }

    protected void PushBasePathToDataBase()
    {
        //Save the value of ___basePath to the database
    }

    public string BasePath
    { //The Binding recieves/sets the Data from/to this property
        get
        {
            return _basePath;
        }
        set
        {
            _basePath = value;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public DATARECORD(string connectionString)
    {
        ConnectionString = connectionString;
        SQL SQL = new SQL(ConnectionString, SQLVersion.CE);
        DataTable storageTable = SQL.fncSelectAsTable(ConnectionString, "tblStorage", "USERID=2");
        storageTable.TableName = "tblStorage";
        currentRecord.Tables.Add(storageTable);
        storageTable.Dispose();
        ___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
    }

    public void fncShowVals(string test)
    {
        MessageBox.Show(currentRecord.Tables["tblStorage"].Rows[0][test].ToString());

    }

    protected void NotifyPropertyChanged(string PropertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
    }
}

Upvotes: 0

Views: 1116

Answers (1)

Noel Widmer
Noel Widmer

Reputation: 4572

It is great that you are using a binding to seperate data from the visuals. Since that was not really possible in winforms. In order for a binding to work you must do the following:

  • The textBox must have its DataContext set to the instance of a class which holds the binding-value. DataContext = MyDataInstance; You can set that on the textbox itself or on any parent.

  • The value as well as the DataContext you want to bind must be a public property. F.e:

    private string _name = null;

    public string Name{ get{ return _name; } set{ _name = value; NotifyPropertyChanged("Name"); } }

  • The Data Class must implement INotifyPropertyChanged

If that is all set up you can write your textbox in xaml:

<TextBlock Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

This Binding binds to the Name property of the instance specified in DataContext. It can retrieve the value from the property and it can write the data to it.

  • It recieves Data when you call NotifyPropertyChanged("Name"); in your DataClass
  • It writes Data when the property of the control changes (Requires Mode set to TwoWay and the UpdateSourceTrigger to PropertyChanged)

EDIT (regarding your additional content)
I noticed that you wanted to notify about your private field named "BasePath".
You must notify the property "basePath" and not the field behind it.
That is why I recommend a strict naming convention.

I do name private and protected fields like _privateOrProtected (1 underscore).
I name private or protected fields accessed by bindings properties like ___someData (3 underscores) and the binding property like SomeData. The reason is, that you usually don't want to set the private field directly except from the binding propertie's setter. Setting it directly would not call the NotifyPropertyChanged(); which obviously isn't what you want in almost all cases. And if you keep the 3 underscores throughout your app - everyone familliar with bindings should quickly understand the meaning.

For more complex data you might have a binding property accessing a private/protected property accessing a private field. I would solve it like this: SomeData, _someData, ___someData. You just have to make it clear wich properties or fields can be set in order to update the binding otherwise someone might change the value of ___someData and wonder why the binding isn't updating.

Since this is a quite important point in every WPF app I really want you to understand it. Here is an example for the stuff above:

private bool ___thisIsAwesome = true;

protected bool _thisIsAwesome{
   get{
      return ___thisIsAwesome;
   }
   set{
      ___thisIsAwesome = value;
      NotifyPropertyChanged("ThisIsAwesome");
   }
}

public bool ThisIsAwesome{ //<- Bind to this property
   get{
      return ___thisIsAwesome;
   }
   /*set{
      _thisIsAwesome = value;
   } NOTE: The setter is not accessable from outside of this class
           because nobody can tell me that this is not awesome - it just is.
           However I still want to be able to set the property correctly 
           from within my class (in case I change my mind), that is why I 
           added the protected property.
           If you omit a getter/setter like this one make sure your 
           <br>Binding Mode</b> does not try to access the omited accessors.
           Also check the output window too find possible binding errors 
           which never throw exceptions. 
   */
}

In this code you should now recognize that setting ThisIsAwesome and _thisIsAwesome will both update the binding. But beware of setting ___thisIsAwesome because it won't update the Binding. The setter of ThisIsAwesome is currently not available (whatever reason) and that's why I added the protected property. Do you understand what I want to achieve with that?

EDIT2 (because your code still doesn't work)

public partial class MainWindow : Window {

        private readonly MyData _data = null;
        public MyData Data{
           get{
              return _data;
           }
        }

        public MainWindow() {
            _data = new MyData();
            DataContext = Data; //All controls connected to this class will now look for their value in 'Data' (DataContext inherits and must be a property because you can only bind to properties)
        }
    }



    public class MyData : INotifyPropertyChanged {    

        private string ___basePath = null;
        private string _basePath {
            get {
                if (___basePath == null) {
                    //We only access the currentRecord if we did not yet stored the value
                    //   otherwise it would read the currentRecord every time you type a char 
                    //   in the textbox.
                    //   Also: Pay attention to multiple possible NullReferenceExceptions and IndexOutOfBoundsExceptions
                    ___basePath = currentRecord.Tables["tblStorage"].Rows[0]["BasePath"].ToString();
                }

                return (___basePath == String.Empty) ? null : ___basePath;
            }
            set {
                ___basePath = (value == null) ? String.Empty : value;
                NotifyPropertyChanged("BasePath");
            }
        }

        protected void PushBasePathToDataBase() {
            //Save the value of ___basePath to the database
        }

        public string BasePath{ //The Binding recieves/sets the Data from/to this property
            get{
                return _basePath;
            }
            set{
                _basePath = value;
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string PropertyName){
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }

        #endregion INotifyPropertyChanged
    }

And finally the textbox in your MainWindow's xaml:

<TextBlock Text="{Binding BasePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Upvotes: 0

Related Questions