2

NARROWED DOWN SOLUTION
I'm much closer, but don't know how to apply XAML to change datacontext value. Please review context of original question below as may be needed.

My issue is that I have a ViewModel class as the datacontext to a window. On this view model, I have a "DataTable" object (with columns and just a single row for testing). When I try to set a Textbox "TEXT" binding to the column of the datatable, it doesn't work. What I've ultimately found is that no matter what "source" or "path" I give it, it just won't cooperate. HOWEVER, just by playing around with scenarios, I said the heck with it. Lets look. The Textbox control has its own "DataContext" property. So, in code, I just FORCED the textbox.DataContext = "MyViewModel.MyDataTableObject" and left the path to just the column it should represent "MyDataColumn", and it worked.

So, that said, how would I write the XAML for the textbox control so it's "DataContext" property is set to that of the datatable object of the view model the window but can't get that correct. Ex:

<TextBox Name="myTextBox" 
    Width="120"
    DataContext="THIS IS WHAT I NEED" --- to represent
    Text="{Binding Path=DataName, 
                    ValidatesOnDataErrors=True,
                    UpdateSourceTrigger=PropertyChanged }" />

DataContext for this textbox should reflect XAML details below and get

(ActualWindow) ( DDT = View Model) (oPerson = DataTable that exists ON the view model) CurrentWindow.DDT.oPerson




I'm stuck on something with binding. I want to bind a column of a datatable to a textbox control. Sounds simple, but I'm missing something. Simple scenario first. If I have my window and set the data context to that of "MyDataTable", and have the textbox PATH=MyDataColumn, all works fine, no problems, including data validation (red border on errors).

Now, the problem. If I this have a same "MyDataTable" as a public on my Window Class directly (but same thing if I had it on an actual ViewModel object, but the window to simplify the level referencing), I can't get it to work from direct XAML source. I knew I had to set the "SOURCE=MyDataTable", but the path of just the column didn't work.

<TextBox Name="myTextBox" 
         Text="{Binding  Source=DDT, Path=Rows[0][DataName], 
                         ValidatesOnDataErrors=True,
                         UpdateSourceTrigger=PropertyChanged }" />

However, from other testing, if I set the path (in code-behind) to

object txt = FindName("myTextBox");
Binding oBind = new Binding("DataName");
oBind.Source = DDT;
oBind.Mode = BindingMode.TwoWay;
oBind.ValidatesOnDataErrors = true;
oBind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
((TextBox)txt).SetBinding(TextBox.TextProperty, oBind);

It DOES work (when the datatable is available as public in the window (or view model))

What am I missing otherwise.

UPDATE: HERE IS A FULL POST of the sample code I'm applying here.

using System.ComponentModel;
using System.Data;

namespace WPFSample1
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public DerivedDataTable DDT;

    public MainWindow()
    {
      InitializeComponent();
      // hook up to a Data Table 
      DDT = new DerivedDataTable();
      DataContext = this;

      // with THIS part enabled, the binding works.  
      // DISABLE this IF test, and binding does NOT.
      // but also note, I tried these same settings manually via XAML.
      object txt = FindName("myTextBox");
      if( txt is TextBox)
      {
        Binding oBind = new Binding("DataName");
        oBind.Source = DDT;
        oBind.Mode = BindingMode.TwoWay;
        oBind.ValidatesOnDataErrors = true;
        oBind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        ((TextBox)txt).SetBinding(TextBox.TextProperty, oBind);
      }
    }
  }

  // Generic class with hooks to enable error trapping at the data table
  // level via ColumnChanged event vs IDataErrorInfo of individual properties
  public class MyDataTable : DataTable
  {
    public MyDataTable()
    {
      // hook to column changing
      ColumnChanged += MyDataColumnChanged;
    }

    protected void MyDataColumnChanged(object sender, DataColumnChangeEventArgs e)
    { ValidationTest( e.Row, e.Column.ColumnName); }

    // For any derived datatable to just need to define the validation method
    protected virtual string ValidationTest(DataRow oDR, string ColumnName)
    { return ""; }
  }

  public class DerivedDataTable : MyDataTable
  {
    public DerivedDataTable()
    {
      // simple data table, one column, one row and defaulting the value to "X"
      // so when the window starts, I KNOW its properly bound when the form shows
      // "X" initial value when form starts
      Columns.Add( new DataColumn("DataName", typeof(System.String))  );
      Columns["DataName"].DefaultValue = "X";

      // Add a new row to the table
      Rows.Add(NewRow());
    }

    protected override string ValidationTest(DataRow oDR, string ColumnName)
    {
      string error = "";
      switch (ColumnName.ToLower())
      {
        case "dataname" :
          if (   string.IsNullOrEmpty(oDR[ColumnName].ToString() )
            || oDR[ColumnName].ToString().Length < 4 )
            error = "Name Minimum 4 characters";

          break;
      }

      // the datarow "SetColumnError" is what hooks the "HasErrors" validation
      // in similar fashion as IDataErrorInfo.
      oDR.SetColumnError(Columns[ColumnName], error);

      return error;
    }
  }
}

AND here's the XAML. Any brand new form and this is the only control in the default "grid" of the window.

Tried following versions, just defining the Rows[0][Column]

<TextBox Name="myTextBox" 
    Width="120"
    Text="{Binding  Path=Rows[0][DataName], 
                    ValidatesOnDataErrors=True,
                    UpdateSourceTrigger=PropertyChanged }" />

Including the source of "DDT" since it is public to the window

<TextBox Name="myTextBox" 
    Width="120"
    Text="{Binding  Source=DDT, Path=Rows[0][DataName], 
                    ValidatesOnDataErrors=True,
                    UpdateSourceTrigger=PropertyChanged }" />

And even suggestions offered by grantnz

2 Answers 2

0

I think your xaml is setting the source to the string "DDT" when you're expecting it to be the property DDT on the current window.

Do you see an error in the output window of Visual Studio like:

System.Windows.Data Error: 40 : BindingExpression path error: 
'Rows' property not found on 'object' ''String' (HashCode=1130459074)'.
BindingExpression:Path=Rows[0][DataName]; DataItem='String' (HashCode=1130459074); 
target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')

If you set the window DataContext to this (from code DataContext = this; or xaml), you can use:

     Text="{Binding  Path=DDT.Rows[0][DataName], 
                     ValidatesOnDataErrors=True,
                     UpdateSourceTrigger=PropertyChanged }" />

or you can leave the DataContext as null and use:

    <TextBox Name="myTextBox" 
     Text="{Binding  RelativeSource={RelativeSource FindAncestor, 
           AncestorType={x:Type Window}},Path=DDT.Rows[0][DataName], 
                     ValidatesOnDataErrors=True,
                     UpdateSourceTrigger=PropertyChanged }" />

The above assumes that you are setting the DDT property before the binding is set-up. If DDT is set after the binding is configured, you'll need to implement INotifyPropertyChanged.

Here's the source of a working version (with DataContext set from XAML and INotifyPropertyChanged implemented). It doesn't work if you comment out the line

OnPropertyChanged(new PropertyChangedEventArgs("DDT"));

and the second TextBox is bound if you leave out the following out of the XAML

DataContext="{Binding RelativeSource={RelativeSource Self}}"

CODE

public partial class MainWindow : Window, INotifyPropertyChanged
{

    public DataTable DDT { get; set; }
    public String SP { get; set; }

    public MainWindow()
    {

        InitializeComponent();
        DDT = new DerivedDataTable();
        OnPropertyChanged(new PropertyChangedEventArgs("DDT"));
        SP = "String prop";
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, e);
    }        

}

XAML

<Window x:Class="BindingTest.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"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">

<StackPanel>
    <TextBox 
     Text="{Binding  RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DDT.Rows[0][DataName], 
                     ValidatesOnDataErrors=True,
                     UpdateSourceTrigger=PropertyChanged }" />
    <TextBox
     Text="{Binding  Path=DDT.Rows[0][DataName], 
                     ValidatesOnDataErrors=True,
                     UpdateSourceTrigger=PropertyChanged }" />
    <TextBox
     Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=SP}" />
    </StackPanel>
</Window>
Sign up to request clarification or add additional context in comments.

9 Comments

Grantnz, no, I was not getting any error on the binging. I tried your Path=DDT.Rows[0][DataName] and that was unsuccessful. I tried including the RelativeSource/FindAncestor too, didn't work. Thanks for suggestions though.
@DRapp - Do you set DDT before or after the binding is configured? If you're setting it from code in the window constructor, try moving the code to above InitializeComponent(); (or implement INotifyPropertyChanged).
@DRapp - I'm surprised you don't see any binding errors in the output window. What happens if you make a deliberate error (e.g. Path=PropertyThatDoesntExist)?
that's the strange thing. It doesn't choke at all. That's why I posted my entire set of code for anyone to sample with. If you set the window's DataContext to the "DDT" during the constructor, and set path to just Path=DataName, then it works that way too.
I found / solved, case-sensitivity as explained in my ultimate answer... Thanks anyhow on your suggestion though.
|
0

SOLVED, but what a PITA... Most things within the samples of doing MVVM patterns will have properties on the view model exposing whatever you want to hook into. When dealing with binding to a DATATABLE (or similar view, etc), you are binding to COLUMNs of said table (or view).

When a table is queried from whatever back-end, the schema populating the data columns will always force the column names to UPPER CASE.

So, if you have a column "InvoiceTotal" in your table, when queried, the column name will have it as "INVOICETOTAL".

If you try to bind to the

Path="InvoiceTotal" ... it will fail

Path="INVOICETOTAL" ... it WILL WORK

However, if you are working directly in .Net (I use C#), the following will BOTH get a value back from the row

double SomeValue = (double)MyTable.Rows[0]["InvoiceTotal"];
or
double SomeValue = (double)MyTable.Rows[0]["INVOICETotal"];
or
double SomeValue = (double)MyTable.Rows[0]["invoicetotal"];

all regardless of the case-sensitivity of the column name.

So, now the rest of the bindings, Error triggers available at the table, row or column levels can properly be reflected in the GUI to the user.

I SURE HOPE this saves someone else the headaches and research I have gone through on this....

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.