Resolved Databinding numericupdown control in usercontrol

rog_rickert

Member
Joined
Dec 17, 2020
Messages
11
Programming Experience
Beginner
I have a databinding issue with a numericupdown control bound to a bindinglist of objects. The updown control is located on a usercontrol. The controls can be loaded with values from a file located in a defaults xml file. The databinding works as it should upon loading the defaults file the first time. If during the session, I want to reload the defaults the databinding is lost. I can see that the values are loaded in correctly, but the numericupdn doesn't change value. here's a simplified version of my code to help explain.

Object class::
public class Garage :
{

    public BindingList<DrawerData> Drawer = new BindingList<DrawerData>
        {
            new DrawerData(),
            new DrawerData()
            
        };
}
    
    public class DrawerData : INotifyPropertyChanged
    {
    private decimal _NumberOfTools;
    public event PropertyChangedEventHandler PropertyChanged;

    public decimal NumberOfTools
        {
            get { return _NumberOfTools; }
            set {
                if (_NumberOfTools != value)
                {
                    _NumberOfTools = value;

                    OnPropertyChanged("NumberOfTools");
                }
            }
        }

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

}

Usercontrol class::
public partial class UcDrawer : UserControl, INotifyPropertyChanged
{

    private readonly Garage _Garage;
    private readonly int Drw
    BindingSource bindingSource = new BindingSource();

    public event PropertyChangedEventHandler PropertyChanged;

    public UcDrawer(int drw,Garage garage)
        {
            _Garage = garage;
            Drw = drw;

            bindingSource.DataSource = _Garage.Drawer[Drw];
            
        }

    private void UcDrawer_Load(object sender, EventArgs e)
        {
            UpDnSettingsNumberOfTools.DataBindings.Add(new Binding("Value", bindingSource, "NumberOfTools", true, DataSourceUpdateMode.OnPropertyChanged));
            
        }

}
Form where usercontrol is used (it's loaded into tabpages in a tabcontrolon the form)::
public partial class FrmGarage : Form
    {
        private readonly int Drw;
        private readonly Garage _Garage;

        public FrmGarage(int drw, Garage garage )
        {
            InitializeComponent();
            Drw = drw;
            _Garage = garage;
        }

        private void FrmGarage_Load(object sender, EventArgs e)
        {
            for (int i = 0; i < ch; i++)
            {
                AddTab(i + 1);
            }
        }

        private void AddTab(int ChNum)
        {
            UcDrawer ucDrawer = new UcDrawer(Drw, _Garage) { Dock = DockStyle.Fill };
            TabPage myTabPage = new TabPage();
            myTabPage.Controls.Add(ucDrawer);
            GarageTabControl.TabPages.Add(myTabPage);

            myTabPage.Name = "Drawer" + Drw;
            myTabPage.Text = "Drawer " + Drw;
        }

        
    }
}
Main form (has buttons on a side list that load different forms into a panel, i'll only include the garage one for simplicity):
public partial class Form1 : Form
    {
        public Garage garage;
        readonly private FrmGarage FrmGarage_vrb;
        private Form ActiveMainForm = null;

        public Form1()
        {
            InitializeComponent();
            garage = new Garage();
            

            FrmGarage_vrb = new FrmGarage(2, garage)    { Dock = DockStyle.Fill, TopLevel = false, TopMost = true };
        }
        

        //Form loader code when one of the side buttons is clicked

        private void MainButtonListClickAction(Button buttonClicked, Form formActivated)
            {
                pnlNav.Height = buttonClicked.Height;
                pnlNav.Top = buttonClicked.Top;
                this.pnlFormLoader.Controls.Clear();
                this.pnlFormLoader.Controls.Add(formActivated);
                formActivated.Show();
                ActiveMainForm = formActivated;
            }


        public void BtnOverride_Click(object sender, EventArgs e)
            {
                MainButtonListClickAction(BtnGarage, FrmGarage);
            }


        //When the New button is pressed,  a defaults file is loaded

        private void BtnMainNew_Click(object sender, EventArgs e)
        {
            

            XmlSerializer serializer = new XmlSerializer(typeof(BindingList<DrawerData>));
            XmlReader reader = XmlReader.Create("defaults.xml");
            garage.Drawer = (BindingList<DrawerData>)serializer.Deserialize(reader);
            
            reader.Close();
            
        
        }
 
This line:
C#:
garage.Drawer = (BindingList<DrawerData>)serializer.Deserialize(reader);
Changes the your Drawer instance, but does nothing to update all of these binding that you had configured:
C#:
bindingSource.DataSource = _Garage.Drawer[Drw];
 
I'm very new to databindings and C# in general, so forgive me if I'm not seeing something. Where would I update the bindings at? I was under the impression that the OnPropertyChanged event in the DrawerData class would send a notification to the control that was bound to it, to update it's value. I put a watch line in the console that showed that the UcDrawer _Garage instance was updated after I loaded the file again, the controls did not however. Any info is appreciated, thank you.
 
When you created the usercontrol you set one of the DrawerData objects in Garage list as bindingsource.datasource. Later you assign new DrawerData objects to the garage, something the usercontrol is unaware of, it's bindingsource.datasource is still referencing the old object. It is not a dynamic link to garage.drawer[ix] whatever that may be, the datasource is the specific object that was assigned in constructor. Usercontrol need to know that it must update its bindingsource.datasource, because now garage has new DrawerData objects.

What you should do is change Drawer public field in Garage class to a readonly property:
C#:
public BindingList<DrawerData> Drawer { get; } = new BindingList<DrawerData>
{
    new DrawerData(),
    new DrawerData()
};
When you deserialize, clear the list and add the new objects to it (or change the objects in-place if you add the same number of items)

In usercontrol listen to garage.Drawer.ListChanged event, when list changes you can reinitialize its bindingsource.datasource.
C#:
garage.Drawer.ListChanged += GarageListChanged;
C#:
void GarageListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
{
    if (Drw < garage.Drawer.Count)
        bindingSource.DataSource = _Garage.Drawer[Drw];
}
You can do this because with the readonly property noone can change the bindinglist object, only its contents.
 
Thanks for the help, could you explain this a little clearer :

"(or change the objects in-place if you add the same number of items)"

I'm not clear on adding the new objects (or changing them in-place) from my deserialize method now that the bindinglist is readonly.
 
Thanks for the help, could you explain this a little clearer :

"(or change the objects in-place if you add the same number of items)"

I'm not clear on adding the new objects (or changing them in-place) from my deserialize method now that the bindinglist is readonly.
It means either use garage.Drawer.Add method or set garage.Drawer[index]
 
Back
Top Bottom