C# 绑定到嵌套对象上的字段

发布于 2024-09-28 13:24:52 字数 1791 浏览 0 评论 0原文

我似乎找不到关于如何使用数据绑定将 WinForms 应用程序中的控件绑定到嵌套对象的简单、具体的解释。例如:

class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name 
    { 
        get { return _Name; } 
        set 
        { 
            _Name = value; 
            OnPropertyChanged("Name"); 
        }    
    }

    private MyInner _Inner;
    public MyInner Inner 
    { 
       get { return _Inner; } 
       set 
       { 
           _Inner = value; 
           OnPropertyChanged("Inner"); 
       } 
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue 
    {
        get { return _SomeValue; } 
        set 
        { 
            _SomeValue = value; 
            OnPropertyChanged("SomeValue"); 
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

现在想象一个只有两个文本框的表单,第一个用于 Name,第二个用于 Inner.SomeValue。我很容易就能绑定到 Name,但 Inner.SomeValue 很不稳定。如果我填充对象然后设置绑定,它会在文本框中显示 Inner.SomeValue 但我无法编辑它。如果我从一个新的对象开始而不初始化 Inner,我似乎无法将数据保留在 Inner.SomeValue 中。

我查遍了 MSDN、StackOverflow,并使用不同的关键字进行了数十次搜索。每个人都想谈论与数据库或 DataGrid 的绑定,并且大多数示例都是用 XAML 编写的。

更新:我已经尝试了马克的完整测试工具并取得了部分成功。如果我点击“全部改变!”按钮,我似乎能够写回到内部对象。但是,从 MyObject.Inner null 开始,它不知道如何创建内部对象。我认为现在,我可以通过确保我的内部引用始终设置为有效对象来解决这个问题。尽管如此,我还是忍不住觉得自己错过了一些东西:)

I can't seem to find a simple, concrete explanation of how to bind controls in a WinForms app to nested objects using data binding. For example:

class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name 
    { 
        get { return _Name; } 
        set 
        { 
            _Name = value; 
            OnPropertyChanged("Name"); 
        }    
    }

    private MyInner _Inner;
    public MyInner Inner 
    { 
       get { return _Inner; } 
       set 
       { 
           _Inner = value; 
           OnPropertyChanged("Inner"); 
       } 
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue 
    {
        get { return _SomeValue; } 
        set 
        { 
            _SomeValue = value; 
            OnPropertyChanged("SomeValue"); 
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Now imagine a form with just two textboxes, the first for Name and the second for Inner.SomeValue. I'm easily able to get binding to work against Name, but Inner.SomeValue is flaky. If I populate the object and then set up the binding, it shows Inner.SomeValue in the textbox but I can't edit it. If I start from a fresh object without initializing Inner, I can't seem to get data to stick in Inner.SomeValue.

I've checked all over MSDN, all over StackOverflow, and dozens of searches with different keywords. Everyone wants to talk about binding to databases or DataGrids, and most examples are written in XAML.

Update: I've tried Marc's full test harness and have partial success. If I hit the "all change!" button, I seem to be able to write back to the inner object. However, starting with MyObject.Inner null, it doesn't know how to create an inner object. I think for now, I can work around it by just making sure my inner references are always set to a valid object. Still, I can't help feeling like I'm missing something :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心如荒岛 2024-10-05 13:24:52

嗯——一个很好的问题;我已经完成了大量与对象的数据绑定,并且我发誓你所做的应该有效;但它确实很不愿意注意到内部对象的变化。我设法让它工作:

var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");

不理想,但它有效。顺便提一句;您可能会发现以下实用程序方法很有用:

public static class EventUtils {
    public static void SafeInvoke(this EventHandler handler, object sender) {
        if(handler != null) handler(sender, EventArgs.Empty);
    }
    public static void SafeInvoke(this PropertyChangedEventHandler handler,
               object sender, string propertyName) {
        if(handler != null) handler(sender,
               new PropertyChangedEventArgs(propertyName));
    }
}

然后您可以拥有:

class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name { get { return _Name; } set {
        _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
    private MyInner _Inner;
    public MyInner Inner { get { return _Inner; } set {
        _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue { get { return _SomeValue; } set {
        _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

并且在讨价还价中,它修复了空异常(竞争条件)的(很小)机会。


完整的测试装置,以解决问题(来自评论):

using System;
using System.ComponentModel;
using System.Windows.Forms;
public static class EventUtils {
    public static void SafeInvoke(this PropertyChangedEventHandler handler, object sender, string propertyName) {
        if(handler != null) handler(sender, new PropertyChangedEventArgs(propertyName));
    }
}
class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name { get { return _Name; } set { _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
    private MyInner _Inner;
    public MyInner Inner { get { return _Inner; } set { _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue { get { return _SomeValue; } set { _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}
static class Program
{
    [STAThread]
    public static void Main() {
        var myObject = new MyObject();
        myObject.Name = "old name";
        // optionally start with a default
        //myObject.Inner = new MyInner();
        //myObject.Inner.SomeValue = "old inner value";

        Application.EnableVisualStyles();
        using (Form form = new Form())
        using (TextBox txtName = new TextBox())
        using (TextBox txtSomeValue = new TextBox())
        using (Button btnInit = new Button())
        {
            var outer = new BindingSource { DataSource = myObject };
            var inner = new BindingSource(outer, "Inner");
            txtName.DataBindings.Add("Text", outer, "Name");
            txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
            btnInit.Text = "all change!";
            btnInit.Click += delegate
            {
                myObject.Name = "new name";
                var newInner = new MyInner();
                newInner.SomeValue = "new inner value";
                myObject.Inner = newInner;
            };
            txtName.Dock = txtSomeValue.Dock = btnInit.Dock = DockStyle.Top;
            form.Controls.AddRange(new Control[] { btnInit, txtSomeValue, txtName });
            Application.Run(form);
        }
    }

}

Hmm - an excellent question; I've done lots of data-binding to objects, and I would have sworn that what you are doing should work; but indeed it is very reluctant to notice the change to the inner object. I've managed to get it working by:

var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");

Not ideal, but it works. Btw; you might find the following utility methods useful:

public static class EventUtils {
    public static void SafeInvoke(this EventHandler handler, object sender) {
        if(handler != null) handler(sender, EventArgs.Empty);
    }
    public static void SafeInvoke(this PropertyChangedEventHandler handler,
               object sender, string propertyName) {
        if(handler != null) handler(sender,
               new PropertyChangedEventArgs(propertyName));
    }
}

Then you can have:

class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name { get { return _Name; } set {
        _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
    private MyInner _Inner;
    public MyInner Inner { get { return _Inner; } set {
        _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue { get { return _SomeValue; } set {
        _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

And in the bargain it fixes the (slim) chance of a null-exception (race-condition).


Full test rig, to iron out kinks (from comments):

using System;
using System.ComponentModel;
using System.Windows.Forms;
public static class EventUtils {
    public static void SafeInvoke(this PropertyChangedEventHandler handler, object sender, string propertyName) {
        if(handler != null) handler(sender, new PropertyChangedEventArgs(propertyName));
    }
}
class MyObject : INotifyPropertyChanged
{
    private string _Name;
    public string Name { get { return _Name; } set { _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
    private MyInner _Inner;
    public MyInner Inner { get { return _Inner; } set { _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}

class MyInner : INotifyPropertyChanged
{
    private string _SomeValue;
    public string SomeValue { get { return _SomeValue; } set { _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
    public event PropertyChangedEventHandler PropertyChanged;
}
static class Program
{
    [STAThread]
    public static void Main() {
        var myObject = new MyObject();
        myObject.Name = "old name";
        // optionally start with a default
        //myObject.Inner = new MyInner();
        //myObject.Inner.SomeValue = "old inner value";

        Application.EnableVisualStyles();
        using (Form form = new Form())
        using (TextBox txtName = new TextBox())
        using (TextBox txtSomeValue = new TextBox())
        using (Button btnInit = new Button())
        {
            var outer = new BindingSource { DataSource = myObject };
            var inner = new BindingSource(outer, "Inner");
            txtName.DataBindings.Add("Text", outer, "Name");
            txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
            btnInit.Text = "all change!";
            btnInit.Click += delegate
            {
                myObject.Name = "new name";
                var newInner = new MyInner();
                newInner.SomeValue = "new inner value";
                myObject.Inner = newInner;
            };
            txtName.Dock = txtSomeValue.Dock = btnInit.Dock = DockStyle.Top;
            form.Controls.AddRange(new Control[] { btnInit, txtSomeValue, txtName });
            Application.Run(form);
        }
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文