Search code examples
c#winformssocketsdata-binding

How can I have controls bound to a property change when the property changes by data received over network?


I've got a class of users with some properties like name, age, gender and message and so I have a form that in it some text boxes created dynamically for each property of each user and I bind each textbox to appropriate properties.

When the users connect to my program and change their properties the text boxes don't change.

This is my user class:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace binding_network
{
    class user : INotifyPropertyChanged
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    NotifyPropertyChanged();
                }
            }
        }
        private int _age;

        public int Age
        {
            get { return _age; }
            set
            {
                if (_age != value)
                {
                    _age = value;
                    NotifyPropertyChanged();
                }
            }
        }
        private string _message;

        public string Message
        {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    NotifyPropertyChanged();
                }
            }
        }
        private string _gender;

        public string Gender
        {
            get { return _gender; }
            set
            {
                if (true)
                {
                    _gender = value;
                    NotifyPropertyChanged();
                }
            }
        }




        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

and this is my form code:

public partial class Form1 : Form
{
    private BindingSource userBindingSource = new BindingSource();
    BindingList<user> userList = new BindingList<user>();
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        ///some code to create textboxes dynamically....

        txtName.DataBindings.Clear();
        txtName.DataBindings.Add("text", userBindingSource[userIndex], "name");
        txtAge.DataBindings.Clear();
        txtAge.DataBindings.Add("text", userBindingSource[userIndex], "age");
        txtGender.DataBindings.Clear();
        txtGender.DataBindings.Add("text", userBindingSource[userIndex], "gender");
        txtMessage.DataBindings.Clear();
        txtMessage.DataBindings.Add("text", userBindingSource[userIndex], "message");
    }
}

And by this method, I receive the data by network

private void GetMessage(object obj)
{
    user user1 = (user)obj;
    try
    {
        while (true)
        {
            byte[] buffer = new byte[1024];
            int rec = user1.SocketClient.Receive(buffer, 0, buffer.Length, 0);
            Array.Resize(ref buffer, rec);
            if (rec > 0)
            {
                user1.Name = BitConverter.ToString(buffer, 0);
                user1.Gender = BitConverter.ToString(buffer, 80);
                user1.Age = BitConverter.ToInt32(buffer, 96);
                user1.Message = BitConverter.ToString(buffer, 160);
            }
        }

    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.ToString());
    }

}

But after receiving the data the textboxes don't refresh


Solution

  • A lot of code appears to be missing, but here's a few things...

    The userBindingSource hasn't been connected to userList.

    The binding propertyName and dataMember parameters have incorrect casing.

    The userIndex isn't defined.

    Even if it were, binding to userBindingSource[userIndex] doesn't allow navigation of the source (maybe you're fine with that).

    So let's fix those:

    public partial class Form1 : Form
    {
        private BindingSource userBindingSource = new BindingSource();
        BindingList<user> userList = new BindingList<user>();
        int userIndex = 0;
    
        public Form1()
        {
            InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            userBindingSource.DataSource = userList;
            userIndex = userBindingSource.Position;
    
            ///some code to create textboxes dynamically....
    
            txtName.DataBindings.Clear();
            txtName.DataBindings.Add("Text", userBindingSource, "Name");
            txtAge.DataBindings.Clear();
            txtAge.DataBindings.Add("Text", userBindingSource, "Age");
            txtGender.DataBindings.Clear();
            txtGender.DataBindings.Add("Text", userBindingSource, "Gender");
            txtMessage.DataBindings.Clear();
            txtMessage.DataBindings.Add("Text", userBindingSource, "Message");
        }
    }
    

    Assuming that userList has been populated, you may now navigate the userBindingSource like this:

    // However you're tracking userIndex, or maybe...
    // userIndex = userList.IndexOf(user1);
    userBindingSource.Position = userIndex;
    

    Or any of these:

    userBindingSource.MoveFirst();
    userBindingSource.MovePrevious();
    userBindingSource.MoveNext();
    userBindingSource.MoveLast();
    

    And finally, remove the infinite while (true) loop in GetMessage.

    At this point, if your data is correctly received and parsed, your TextBox controls should update.

    Edit...

    So you're multi-threading this, which is great.

    Now we have to make sure all operations that result in a UI change are done on the proper thread.

    Lets do this (assuming GetMessage is defined inside the Form class):

            if (rec > 0)
            {
                var name = BitConverter.ToString(buffer, 0);
                var gender = BitConverter.ToString(buffer, 80);
                var age = BitConverter.ToInt32(buffer, 96);
                var message = BitConverter.ToString(buffer, 160);
    
                this.Invoke(new Action(() =>
                {
                    user1.Name = name;
                    user1.Gender = gender;
                    user1.Age = age;
                    user1.Message = message;
                }));
            }
    

    And this:

    catch (Exception ex)
    {
        this.Invoke(new Action(() => MessageBox.Show(ex.ToString())));
    }
    

    And binding source navigation (if on a different thread):

    this.Invoke(new Action(() => userBindingSource.Position = userIndex));
    

    You may also look into using the BeginInvoke method.