【问题标题】:WPF bind Textbox to a DataSet in another classWPF 将文本框绑定到另一个类中的数据集
【发布时间】:2015-04-02 23:37:03
【问题描述】:

WPF 通常不是我的领域,所以我有点新手,我在弄清楚如何在 WPF 中实现某些东西时遇到了一些麻烦,而这在 WinForms 中是小菜一碟。我似乎无法在此论坛中找到正确的主题或正确的 YouTube 教程来引导我找到答案。我在让 WPF TextBox 的简单 DataBinding 正常工作时遇到问题。我试图实现的行为是对 TextBox 所做的任何更改都会立即反映在源类 DataSet 中。这是一个简单的显示/编辑场景,我相信有一个非常简单的答案。

这就是我在 WinForms 中的做法......

表格代码:

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

类代码:

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

然后我在主窗体上只有一个名为“CtlCopiesToKeep”的简单文本框和一个“测试”按钮。

有没有人知道一个很好的、简单的例子可以说明如何做到这一点?

提前非常感谢, 戴夫

编辑:

你好诺埃尔。非常感谢您花时间解释这一切。我已经把它放在一起了,但是绑定似乎有问题,因为当我更改 TextBox 中的值时,它不会更新 DataSet。这是代码和 XAML。如果有人能指出我正确的方向,那将不胜感激。

更新的主代码

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

更新的类代码

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

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

2015 年 2 月 4 日更新

我现在有了这个,但我不明白它是如何引用数据集的?此代码生成一个空白文本框,如果值发生更改,它不会更新 DataSet:

`私有字符串___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;
        }
    }`

底层数据集值存储在这里:

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

非常感谢,戴夫。

更新 - 2015 年 2 月 4 日 - 2

您好 Noel,我已经应用了您的代码,但不幸的是它仍然无法正常工作(如果我单击“测试”按钮,DataSet 不会反映 TextBox 中的更改)。这是整个代码。顺便说一句,我非常感谢您花时间在这方面,非常感谢!

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

【问题讨论】:

  • 你能发布你的 XAML 吗?

标签: wpf textbox dataset


【解决方案1】:

很高兴您使用绑定将数据与视觉效果分开。因为这在winforms中是不可能的。为了使绑定起作用,您必须执行以下操作:

  • 文本框必须将其 DataContext 设置为保存绑定值的类的实例。 DataContext = MyDataInstance; 您可以在文本框本身或任何父级上设置它。

  • 要绑定的值和 DataContext 必须是公共属性。 F.e:

    private string _name = null;

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

  • 数据类必须实现INotifyPropertyChanged

如果一切就绪,您可以在 xaml 中编写文本框:

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

此 Binding 绑定到 DataContext 中指定的实例的 Name 属性。它可以从属性中检索值并将数据写入其中。

  • 当您在 DataClass 中调用 NotifyPropertyChanged("Name"); 时,它会接收数据
  • 当控件的属性发生变化时写入数据(需要将Mode设置为TwoWay,将UpdateSourceTrigger设置为PropertyChanged

编辑(关于您的附加内容)
我注意到您想通知您名为 "BasePath" 的私有字段。
您必须通知属性"basePath" 而不是它后面的字段。
这就是为什么我建议使用严格的命名约定。

我确实将私有和受保护字段命名为 _privateOrProtected(1 个下划线)。
我命名了由___someData(3 个下划线)之类的绑定属性和SomeData 之类的绑定属性访问的私有或受保护字段。原因是,您通常不想直接设置私有字段,除非从绑定属性的设置器中。直接设置它不会调用NotifyPropertyChanged();,这在几乎所有情况下显然不是您想要的。如果您在整个应用程序中保留 3 个下划线 - 每个熟悉绑定的人都应该很快理解其含义。

对于更复杂的数据,您可能有一个绑定属性访问一个私有/受保护的属性来访问一个私有字段。我会这样解决它:SomeData_someData___someData。您只需要明确可以设置哪些属性或字段以更新绑定,否则有人可能会更改 ___someData 的值并想知道为什么绑定没有更新。

由于这是每个 WPF 应用程序中非常重要的一点,我真的希望您理解它。以下是上述内容的示例:

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. 
   */
}

在这段代码中,您现在应该认识到设置ThisIsAwesome_thisIsAwesome 都会更新绑定。但要注意设置___thisIsAwesome,因为它不会更新绑定。 ThisIsAwesome 的设置器当前不可用(无论出于何种原因),这就是我添加受保护属性的原因。你明白我想要达到什么目的吗?

EDIT2(因为您的代码仍然不起作用)

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
    }

最后是 MainWindow 的 xaml 中的文本框:

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

【讨论】:

  • 感谢 Noel,我已经更进一步,并在上面发布了新代码和 XAML。我将不胜感激您对如何使绑定 100% 正常工作的想法。再次感谢戴夫
  • @the_cat21 您传递给 OnPropertyChanged() 的属性名称区分大小写“basepath”与“BasePath”不同。确保字符串与您的属性的名称相同,否则 UI 不会对其进行操作。
  • @the_cat21 扩展了我的答案很多
  • 您好 Noel,再次感谢您的详尽解释。我理解您所说的主要内容,并且您的代码策略非常合乎逻辑。但是,请参阅我对原始帖子的“编辑”。我不明白底层 DataSet 值如何适合等式。非常感谢,戴夫。
  • @the_cat21 见编辑。我猜你的 dataContext 不是属性?无论如何,我也做了一些性能改进......
猜你喜欢
  • 2013-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 2010-10-20
  • 2011-06-27
  • 1970-01-01
相关资源
最近更新 更多