Home > Software design >  How to Bind TextBox from Multiple Other Controls
How to Bind TextBox from Multiple Other Controls

Time:01-29

What I Have

I have a form with 3 UserControls that contain source fields to be used to calculate another field in a target UserControl.

UserControl1 (uc1)

  • StartDate DatePicker (dpStart)
  • EndDate DatePicker (dpEnd)

UserControl2 (uc2)

  • Rate TextBox (txtRate)

UserControl3 (uc3)

  • Amount TextBox (txtAmount)

UserControl 4 (uc4)

  • CalculatedValue TextBox (txtCalc)

Formula to calculate txtCalc is below

txtCalc.Text = (dpEnd.Value - dpStart.Value).TotalDays * txtRate.Text * txtAmount.Text

What I Want To Achieve

Whenever I change a value in any of the first 3 UserControls, the Text in txtCalc should update with calculated value.

What I Have Tried

I have tried DataBindings but it appears to be only for a single field.

e.g.

uc4.txtAmount.DataBindings.Add("Text", uc2.txtRate, "Text", true, DataSourceUpdateMode.Never);

Would really appreciate your inputs.

CodePudding user response:

There are a few things you may want to consider to bind the date pickers and the text boxes as you want. In addition there are a couple of ways you can do this.

One possible problem is that each of the UI “controls” (date pickers and text boxes) are spread out across four (4) different UserControls. This is not a big problem and about the only thing you need to make sure is that each of the “controls” (DTP and TextBoxes) in each of the UserControls is “exposed” to the Main Form.

In other words, if you simply place a TextBox in a UserControl and then place that UserControl onto the Main Form… then the Main Form will NOT be able to access that TextBox … UNLESS it is “exposed.” This can be remedied by simply setting the TextBoxes Modifier property to Public. Then the Main Form WILL have access to the text box in the UserControl.

Therefore, step one is make sure each UI “control” in each of the UserControls that we want to access in the Main Form has its Modifier property set to Public. The reason for this, is that you want to be able to access the data in those controls from the Main Form. In addition to the fact that we want to subscribe to those controls “Events” FROM the Main Form.

What we will do inside the Main Form after the UserControl is placed onto the form is subscribe to the UI Control’s (DTP or TextBox) TextChanged event. Then we can “capture” when one of the controls text changes… and we can capture this change INSIDE the Main Form.

Another possible issue is how the code is calculating the “total days” amount from the two DateTimePickers. The current code looks something like…

(dpEnd.Value - dpStart.Value).TotalDays

This will work, however, if you debug the code and look closely at the result of the calculation you may note that if date 1 is 1/25/2021 and date 2 is 1/26/2021… then you apply the code above… there is a good chance that you may get a result like 0.933 … which will obviously become a 0 when converted to an int and this is not what we would expect. The reason for this is because when you add or subtract two DateTime objects… the calculation is INCLUDING the Time portion of the DateTime object.

Therefore, to get the correct int value… you need to “qualify” that you only want the “Date” difference between the two dates. Fortunately the DateTime object has a Date property that we can use to ignore the Time portion. Therefore only a small change is needed to fix this and may look something like…

(dpEnd.Value.Date - dpStart.Value.Date).TotalDays

As suggested in the comments, using Events is probably the easiest to implement and it is not difficult to understand. Basically, we would subscribe (wire-up) each of the controls in each of the UserControls to the SAME event. Inside that event we would “update” the calculated value.

Typically, you would wire up each control to its own event, however, since you want to simply “update” a single text box when ANY of the control’s changes we can simplify this and create a single method to “update” the calculated text box when ANY of the other controls changes. I hope that makes sense.

To help, and I highly recommend you also (in the near future) do the same… is properly NAME your variables. Naming the controls uc1, uc2, uc3 and uc4 is well … not a good idea. You can do it, but it makes it difficult to tell “what” the control is. Looking at the names… without more research, I have no idea “which” control has the “Rate” text box. Name your variables to something meaningful to avoid any ambiguity. In the example below, for the UserControls I named them like… UC_StartEndDate, UC_Rate etc…

Another possible issue is that since you are wanting to perform a “calculation,” you will need to parse the string values in the TextBoxes to int values. In other words… the code…

txtRate.Text * txtAmount.Text

May well work without an error, however I am confident it will not give you the result you want since both sides of the “*” multiplier are “TEXT/string” values and we will need to parse those string values to int values to do any mathematical calculations.

NOTE the int.TryParse(UC_Rate.txtRate.Text, out int rate); line of code would typically be wrapped in an if statement since it will return true if the parse succeeded and false if it fails. If the parse fails, then the out variable rate will be set to zero (0) and that is ok with me… if it fails then use a zero (0) as the value. You may want to do something different.

private void UpdateUC_Total() {
  int tdays = (int)(UC_StartEndDate.dpEnd.Value.Date - UC_StartEndDate.dpStart.Value.Date).TotalDays;
  int.TryParse(UC_Rate.txtRate.Text, out int rate);
  int.TryParse(UC_Amount.txtAmount.Text, out int amt);
  int total = tdays * rate * amt;
  UC_Calculated.txtCalc.Text = total.ToString();
}

Now all we have to do is subscribe (wire-up) to the individual UI controls TextChanged event. As already mentioned, since we want ALL the controls to do the same calculation, this simplifies things and we can have ALL the controls subtribe to the SAME event. This one event would simply call our method above and may look something like…

private void UC_ValueChanged(object sender, EventArgs e) {
  UpdateUC_Total();
}

In the forms Load event, we could subscribe the controls in each of the UserControls to our event above and it may look something like…

private void Form1_Load(object sender, EventArgs e) {
  UC_StartEndDate.dpStart.TextChanged  = UC_ValueChanged;
  UC_StartEndDate.dpEnd.TextChanged  = UC_ValueChanged;
  UC_Rate.txtRate.TextChanged  = UC_ValueChanged;
  UC_Amount.txtAmount.TextChanged  = UC_ValueChanged;
}

That is pretty much it. If any of the date pickers or rate or amount text boxes change, then the calculated text box text will “update” automatically. You may need to “leave” the control to see the updated value.


Another approach is to use each control’s DataBindings property to “Bind” each control to something. Currently you have the code…

uc4.txtAmount.DataBindings.Add("Text", uc2.txtRate, "Text", true, DataSourceUpdateMode.Never);

The problem here is that the data bindings is looking for a DataSource of some type like a DataTable or a List<T> or in the example below a Class object. The code “appears” to be using uc2.txtRate as a DataSource and this probably will not work as txtRate is a TextBox and is not necessarily a DataSource.

This can get tricky, and to keep things simple, I will often simply “create” a DataSource to make it easier to set the controls DataBindings to work as I want. The code below shows how you could do this “DataBinding” using your current example. Bear in mind this will work, however, it may create a little more work for you.

So step 1 is to “create” a simple DataSource we can use for ALL the controls (DTP and TextBoxes). In this case I will implement a single Class with the properties we need. Then, we would instantiate only ONE of these objects and then use that object as a DataSource when setting each controls DataBindings property. This UC_Helper class may look something like…

public class UC_Helper {
  public DateTime DP_Start { get; set; }
  public DateTime DP_End { get; set; }
  public string Rate { get; set; }
  public string Amount { get; set; }
  public int CalcAmount {
    get {
      int tdays = (int)(DP_End.Date - DP_Start.Date).TotalDays;
      int.TryParse(Rate, out int rate);
      int.TryParse(Amount, out int amt);
      return  tdays * rate * amt;
    }
  }
}

We will be able to instantiate a single UC_Helper object with the values from our user control “controls” and then simply set each control’s DataBinding property to “point” to the proper property in the instantiated UC_Helper object.

Therefore in the form load event, you would instantiate a new UC_Helper object and then “bind” each of the controls in each of the UserControls to one of the properties in our UC_Helper object. This code in the forms Load event may look something like…

UC_Helper ControlHelper;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  ControlHelper = new UC_Helper {
    DP_Start = UC_StartEndDate.dpStart.Value.Date,
    DP_End = UC_StartEndDate.dpEnd.Value.Date,
    Rate = UC_Rate.txtRate.Text,
    Amount = UC_Amount.txtAmount.Text
  };
  UC_Amount.txtAmount.DataBindings.Add("Text", ControlHelper, "Amount");
  UC_Rate.txtRate.DataBindings.Add("Text", ControlHelper, "Rate");
  UC_Calculated.txtCalc.DataBindings.Add("Text", ControlHelper, "CalcAmount");
  UC_StartEndDate.dpStart.DataBindings.Add("Text", ControlHelper, "DP_Start");
  UC_StartEndDate.dpEnd.DataBindings.Add("Text", ControlHelper, "DP_End");
}

Sorry for the long post. I hope this helps and I suggest you pick you own poison as to which approach to use. I tend to favor the event approach, however if there is a good DataSource available I may go with the data binding. Good Luck.

  •  Tags:  
  • Related