【问题标题】:WPF Binding DataTable Row Column to textboxWPF将DataTable行列绑定到文本框
【发布时间】:2012-05-09 06:29:37
【问题描述】:

缩小范围的解决方案
我更接近,但不知道如何应用 XAML 来更改数据上下文值。请根据需要查看以下原始问题的上下文。

我的问题是我有一个 ViewModel 类作为窗口的数据上下文。在这个视图模型上,我有一个“DataTable”对象(带有列和只有一行用于测试)。当我尝试将文本框“TEXT”绑定设置到数据表的列时,它不起作用。我最终发现的是,无论我给它什么“来源”或“路径”,它都不会合作。但是,只是通过玩一些场景,我说了算。我们看看吧。文本框控件有自己的“DataContext”属性。所以,在代码中,我只是强制 textbox.DataContext = "MyViewModel.MyDataTableObject" 并将路径留给它应该代表 "MyDataColumn" 的列,并且它起作用了。

也就是说,我将如何为文本框控件编写 XAML,以便将其“DataContext”属性设置为窗口视图模型的数据表对象的属性,但无法正确设置。例如:

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

此文本框的 DataContext 应反映下面的 XAML 详细信息并获取

(ActualWindow) (DDT = 视图模型) (oPerson = 存在于视图模型上的数据表) CurrentWindow.DDT.oPerson




我被绑定的东西困住了。我想将数据表的列绑定到文本框控件。听起来很简单,但我错过了一些东西。先说简单的场景。如果我有我的窗口并将数据上下文设置为“MyDataTable”,并且文本框 PATH=MyDataColumn,那么一切正常,没有问题,包括数据验证(错误为红色边框)。

现在,问题来了。如果我直接在我的 Window 类上有一个与 public 相同的“MyDataTable”(但如果我在一个实际的 ViewModel 对象上有它,但窗口是为了简化级别引用),我无法让它工作直接 XAML 源。我知道我必须设置“SOURCE=MyDataTable”,但只是列的路径不起作用。

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

但是,从其他测试来看,如果我将路径(在代码隐藏中)设置为

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);

它确实有效(当数据表在窗口(或视图模型)中作为公共可用时)

否则我错过了什么。

更新:这是我在此处应用的示例代码的完整帖子。

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;
    }
  }
}

这里是 XAML。任何全新的窗体,这是窗口默认“网格”中的唯一控件。

尝试了以下版本,只定义了 Rows[0][Column]

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

包括“DDT”的来源,因为它对窗口是公开的

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

甚至是grantnz提供的建议

【问题讨论】:

    标签: wpf binding datatable viewmodel


    【解决方案1】:

    我认为您的 xaml 将源设置为字符串“DDT”,而您希望它是当前窗口上的属性 DDT。

    您是否在 Visual Studio 的输出窗口中看到如下错误:

    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')
    

    如果将窗口 DataContext 设置为 this(来自代码 DataContext = this; 或 xaml),则可以使用:

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

    或者您可以将 DataContext 保留为 null 并使用:

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

    以上假设您在设置绑定之前设置 DDT 属性。如果在配置绑定后设置了 DDT,则需要实现 INotifyPropertyChanged。

    这里是工作版本的来源(从 XAML 设置 DataContext 并实现了 INotifyPropertyChanged)。注释掉行不行

    OnPropertyChanged(new PropertyChangedEventArgs("DDT"));
    

    如果您在 XAML 中省略以下内容,则第二个 TextBox 将被绑定

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

    代码

    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>
    

    【讨论】:

    • Grantnz,不,我在 binging 上没有收到任何错误。我尝试了您的 Path=DDT.Rows[0][DataName] ,但没有成功。我也尝试包括RelativeSource/FindAncestor,但没有奏效。不过感谢您的建议。
    • @DRapp - 您是在配置绑定之前还是之后设置 DDT?如果您是从窗口构造函数中的代码设置它,请尝试将代码移至 InitializeComponent(); (或实现 INotifyPropertyChanged)。
    • @DRapp - 我很惊讶您在输出窗口中没有看到任何绑定错误。如果您故意犯错误(例如 Path=PropertyThatDoesntExist)会怎样?
    • 这很奇怪。它根本不会窒息。这就是为什么我发布了我的整套代码供任何人使用。如果在构造函数期间将窗口的 DataContext 设置为“DDT”,并将路径设置为 Path=DataName,那么它也可以这样工作。
    • 我发现/解决了,区分大小写,正如我的最终答案中所解释的那样......无论如何感谢您的建议。
    【解决方案2】:

    已解决,但是 PITA... 执行 MVVM 模式的示例中的大多数内容都会在视图模型上具有属性,从而暴露您想要挂钩的任何内容。在处理绑定到 DATATABLE(或类似视图等)时,您将绑定到所述表(或视图)的 COLUMN。

    当从任何后端查询表时,填充数据列的架构将始终强制列名为大写。

    因此,如果您的表中有“InvoiceTotal”列,则在查询时,该列名称将显示为“INVOICETOTAL”。

    如果你尝试绑定到

    Path="InvoiceTotal" ... it will fail
    
    Path="INVOICETOTAL" ... it WILL WORK
    

    但是,如果您直接在 .Net 中工作(我使用 C#),则以下内容都将从行中返回一个值

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

    无论列名是否区分大小写。

    所以,现在其余的绑定,在表、行或列级别可用的错误触发器可以正确地反映在 GUI 中给用户。

    我当然希望这可以为其他人省去我对此进行的头痛和研究....

    【讨论】:

      猜你喜欢
      • 2015-12-03
      • 2011-07-17
      • 2013-07-28
      • 2011-05-11
      • 2018-02-07
      • 2023-04-06
      • 2011-10-13
      • 2016-02-12
      • 1970-01-01
      相关资源
      最近更新 更多