Pregunta

Me parece que no puede encontrar una sencilla explicación concreta de cómo se unen los controles en una aplicación Windows Forms a objetos anidados utilizando el enlace de datos. Por ejemplo:

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

Ahora imagina un formulario con sólo dos cuadros de texto, el primero para el nombre y el segundo para Inner.SomeValue. Estoy fácilmente capaz de conseguir la unión al trabajo contra el Nombre, pero Inner.SomeValue es escamosa. Si rellenar el objeto y luego configurar la unión, se nota Inner.SomeValue en el cuadro de texto pero no puede editarlo. Si comienzo a partir de un objeto sin inicializar fresco interior, me parece que no puede obtener datos a palo en Inner.SomeValue.

he comprobado todo MSDN, todo Stackoverflow, y de las búsquedas con diferentes palabras clave docenas. Todo el mundo quiere hablar de unión a bases de datos o DataGrids, y la mayoría de los ejemplos están escritos en XAML.

Actualización: He intentado instrumento de prueba completo de Marc y tienen un éxito parcial. Si lo golpeo el "todo cambio!" botón, que parece ser capaz de escribir de nuevo al objeto interno. Sin embargo, a partir de MyObject.Inner nula, que no sabe cómo crear un objeto interno. Creo que por ahora, puedo trabajar alrededor de ella con sólo hacer que mis referencias internas siempre se establecen en un objeto válido. Aún así, no puedo evitar sentir que me estoy perdiendo algo:)

¿Fue útil?

Solución

Hmm - una excelente pregunta; He hecho muchas enlace de datos a objetos, y tendría que jurada que lo que está haciendo debe trabajar; pero de hecho es muy reacio a notar el cambio en el objeto interno. Me las he arreglado para que funcione por:

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

No es ideal, pero funciona. Por cierto; es posible encontrar los siguientes métodos de utilidad útil:

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

A continuación, puede tener:

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

Y en el trato que fija la oportunidad (delgada) de un nulo excepción (carrera de condición).


banco de pruebas completo, para limar torceduras (de comentarios):

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

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top