I have a question regarding updating Controls in .Net in such a way so that if the user updates one field, the other field will be updated with some data automatically and vice versa. I am using two NumericUpDown controls which convert some data.
The problem I am having is that, I am using the ValueChanged event. Because of this, sometimes these controls get stuck in a loop where one controls updates the other and the other tries to update the first one. The result is somewhat random.
So, what would be the best approach to handle this situation? In short, I only want to update the other control, if the first one was modified by a user himself.
Any help would be greatly appreciated.
Just use a boolean guard in the class to check whether you are inside an update method.
While you are updating, all future events fired from the NUDs will be ignored.
private boolean updating = false; // Class level variable
void event_handler(...) // The function hooked up to the ValueChanged event
{
if( !updating )
{
updating = true;
// Do your calculations and update the NUDs
updating = false;
}
}
I would recommend that you use data binding and bind to an object which serves as your model. Your model then is where the logic goes that alters the other value based on changing of a property. The model also raises IPropertyChanged / IPropertyChanging events, which the UI will pick up on. Not only will this prevent the issue you describe, it also keeps this business logic outside of your UI layer should you move to something else (say from WinForms to WPF, or Asp.Net MVC).
If method Foo handles the event of one control and method Bar handles the event for the other, then Foo should change the values of Bar's control and vice-versa. But you should use a control variable somewhere (say, a reference to the control that fired the event is a good idea). So that if Foo is called:
Foo updates the value of Bar's control;
Bar's control fires its event, and Bar is called;
Bar checks the reference for the control that shot first, sees that it's not its control, and does nothing.
Same logic applies to Bar.
that way you don't get an infinite loop.
In code, it'd, look like this:
nud1.ValueChanged += new Eventhandler(Foo);
nud2.ValueChanged += new Eventhandler(Bar);
NumericUpDown shooter = null;
private void Foo (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud1;
nud2.Value = nud1.Value;
}
else this.shooter = null;
}
private void Bar (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud2;
nud1.Value = nud2.Value;
}
else this.shooter = null;
}
Of course, this is a crude example (for example, it assumes the value of both controls is always changing. Adapt to your situation.
I like Andy's response about going with an MVC pattern, but if that's too radical of a change for this specific situation, you should set the values only if the current value is different than the value being assigned. That would prevent the ValueChanged event from firing again and stop the infinite loop the first time recursion happens.
// Inside your value changed handler for Control2,
// instead of directly setting the value of Control1, do this:
if(Control1.Value != valueBeingSet)
{
Control1.Value = valueBeingSet;
}
Related
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.
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.
I decided to try a third-party's implementation of the DataGridView, specifically for the purpose of taking advantage of hierarchical grids, i.e. "Grid within a grid" functionality. While this is working out fairly well, I noticed some of the events and properties I depend on with normal DataGridViews don't exist in this implementation, and had to discover different ways to accomplish the same goals.
One such goal was that, when I use a CheckBoxColumn and the user "checks" or "un-checks" the box, I need to push that change to the grid (and subsequently the database) when it happens, otherwise the user would have to hit enter or leave the cell focus to actually commit the change.... Kind of wonky. To remedy this, I took advantage of the 'CurrentCellDirtyStateChanged' event, and manually committed the check or un-check to the grid, which in turn fired my 'CellValueChanged' event:
void dataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if ((dataGridView.IsCurrentCellDirty) & (dataGridView.CurrentCell.ColumnIndex == 0))
{
dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
However, this event doesn't exist with the third-party grid, and neither does the CommitEdit method. In order to achieve the same functionality, I ended up having to do this:
void GridView_ValueChanged(object sender, EventArgs e)
{
if(sender.GetType().ToString().Contains("RadCheckBoxEditor"))
{
RadCheckBoxEditor rad_chb = (RadCheckBoxEditor)sender;
GridCheckBoxCellElement checked_a_box = (GridCheckBoxCellElement)rad_chb.OwnerElement;
checked_a_box.Value = rad_chb.Value;
GridViewCellEventArgs new_args = new GridViewCellEventArgs(checked_a_box.RowInfo, checked_a_box.ColumnInfo, checked_a_box.Editor);
GridView_CellValueChanged(checked_a_box, new_args);
}
else
{
return;
}
}
I essentially caught the dirty value, assigned it to the value of a type recognizable by 'CellValueChanged' handler, defined my own event args, and explicitly called the handle. Are there any potential smells to something like this? It just feels wrong. I noticed others suggested making an independent function to access shared code, yet, those examples didn't seem to depend on a specific event pushing an event.
My scenerio is like this:
At runtime, I bind ToolStripComboBox to array of struct:
cbxTimes.ComboBox.DataSource = PlayTimeLengths;
cbxTimes.ComboBox.DisplayMember = "Description";
cbxTimes.ComboBox.ValueMember = "Minutes";
The DropDownStyle of ToolStripCombobox is set to DropDown.
Everything is working fine, I can select values from the dropdown list and I can write text in the control.
However I wanted to prevent user from pressing some controls and alternate the Text property when some other controls are pressed.
I am trying to accomplish this in KeyPress event:
private void cbxTimes_KeyPress(object sender, KeyPressEventArgs e)
{
var cbxSender = ((ToolStripComboBox)sender).ComboBox;
string S = cbxSender.Text;
//some operations on the S variable
cbxSender.Text = S;
e.Handled = true;
} // breakpoint here shows that cbxSender.Text is not changed to S!
So the Text property has not been changed but I didn't get any exception.
However, if I run the program further (I quit from the debugging) I see that the Text property is changed - to be more specific. I see the text from S inside the control.
Now, imagine that I press any key for the second time, and again I am in the debugger in the same event:
private void cbxTimes_KeyPress(object sender, KeyPressEventArgs e)
{
var cbxSender = ((ToolStripComboBox)sender).ComboBox;
string S = cbxSender.Text; // this time breakpoint is here
//some operations on the S variable
cbxSender.Text = S;
e.Handled = true;
} // breakpoint here shows that cbxSender.Text is not changed to S!
But this time I put breakpoint on the second line and after examining the Text property I see that it still has not changed. Despite the fact that I've altered it on the first time when the event was fired up and the altereted text is visible in the control. But under debugger I see different value, I see value that has been set up at the begining. Value which belongs to the array of structs.
SO what can I do to overcome this problem?
Honestly this is one of the things I hate about Windows Forms databinding. In WPF you would not bind to the objects directly, you'd bind to a "ViewModel" object which encapsulated this view logic you have and bind to it instead.
My workaround to all of this would be to just not use databinding for this case at all and manually populate the items as needed. I can understand why you might be having this problem. If you had updated your underlying bound object's .Text (or whatever causes ToString() to display the value), you would probably see the new value you'd set, but that upsets the semantics of your underlying objects, which is Not A Good Thing.
I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown
I have a WinForms ListView, obviously containing ListViewItems. I'd like to be able to attach a click event to each item, instead of to the entire ListView (and then trying to figure out what item was clicked). The reason for this is that I need to perform a different action based on which item was selected. The ListViewItem class seems to be very limited in this regard. Is there any way to do what I want, or am I forced to use the ListView.Click event?
I would still use the ListView Click event.
A trick I've used in these situations is to use the Tag property of a ListViewItem. It's great for storing per item data and you can put anything in it.
It may make sense to subclass ListViewItem and use virtual dispatch to select the appropriate behavior based on the selected ListViewItem in the appropriate ListView event.
E.g. (uncompiled)
public abstract class MyItems : ListViewItem
{
public abstract DoOperation();
}
public class MyItemA : MyItems
{
public override DoOperation()
{ /* whatever a */ }
}
public class MyItemB : MyItems
{
public override DoOperation()
{ /* whatever b */ }
}
// in ListView event
MyItems item = (MyItems)this.SelectedItem;
item.DoOperation();
As others have mentioned, it may also make sense to use the appropriate Tag property. Which technique you go for really depends on what your action is (and therefore where it belongs, architecturally). I assumed the subclass made more sense because you're looking for a click on a listview item, and that (to me) seems more likely to be presentation-layer b/c you're overriding some standard control behavior (which would normally just select an item) as opposed to doing something in response to behavior.
In most use cases, a ListViewItem is a representation in the UI of some object, and what you're trying to do is execute a method of the object that the ListViewItem represents when the user clicks on it. For the sake of simplicity and maintainability, you want as few things to sit between the user's mouse-click and the actual method being executed.
You can store the object in the ListViewItem's Tag property and then reference it in the Click event handler, but that results in code that's got some inherent weak points:
private void MyListView_Click(object sender, EventArgs e)
{
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClass obj = l.SelectedItem.Tag as MyClass;
if (obj != null)
{
obj.Method();
}
}
}
That's a lot of casting and null-reference checking. And the really weak thing about this code is that if it turns out that Tag is null, or contains something other than a MyClass object, you don't really know where to look to find out where the problem is occurring.
Contrast it with code like this:
private void MyListView_Click(object sender, EventArgs e)
{
MyClass.ListViewClicked(sender as ListView);
}
When you're maintaining this code, you don't know how that ListViewClicked method is implemented, but at least you know where to look for it - in MyClass. And when you do, you'll see something like this:
public static void ListViewClicked(ListView listView)
{
if (listView.SelectedItem == null)
{
return;
}
if (ListViewItemLookup.ContainsKey(listView.SelectedItem))
{
ListViewItemLookup[listView.SelectedItem].Execute();
}
}
Well, that's interesting. Following the thread, how does that dictionary get populated? You find that in another method in MyClass:
private static Dictionary<ListViewItem, MyClass> ListViewItemLookup =
new Dictionary<ListViewItem, MyClass>();
public ListViewItem GetListViewItem()
{
ListViewItem item = new ListViewItem();
item.Text = SomeProperty;
// population of other ListViewItem columns goes here
ListViewItemLookup.Add(item, this);
return item;
}
(Reasonable people can disagree about whether or not it's appropriate for a class to be so closely tied to a specific form of its representation in the UI - there are those who would isolate these methods and this dictionary in a helper class instead of in MyClass itself, and depending on how hairy the rest of the problem is I might do it too.)
This approach solves a number of problems: it gives you a simple way of handling the ListView's Click event properly, which is what you asked for. But it also isolates the not-always-trivial process of creating the ListViewItem in the first place. It reduces the amount of code you'll have to move around if you refactor your form and move the ListView to another form. And it reduces the number of things that your form class needs to know about, which is generally a good thing.
Also, it's testable. Generally, the only way to test code in a UI event handler is through the UI. This approach lets you isolate all of the logic surrounding this part of the UI in something that you can unit test; the only thing you can't write a unit test for is a single line of code in the form.
I should point out that the other approach people have been suggesting - subclassing ListViewItem - is perfectly fine too. You put the logic I put in the GetListViewItem method in the class's constructor, make the MyClass instance a private property of the class, and expose a Click method that calls the method of MyClass. Pretty much the only reason I don't like it is that it still leaves you with a fair amount of code in your form that you can't really unit test:
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClassListViewItem item = l.SelectedItem as MyClassListViewItem;
if (item != null)
{
item.MyClass.Method();
}
}
You might however have luck sticking a reference to a delegate or other handler in the tag field (assuming there is a tag property of a ListViewItem). You would still have to determine which ListViewItem is clicked, but you could then go straight to the tag instead of another decision structure.
You want to create a new class (or classes if there are various types), which inherits from ListViewItem, then populate your ListView with these objects (as long as they inherit from listview (even several levels of inheritence) The ListView control will take them).
Then add a click method to your custom class(es) and on the ItemClick event of your listView, just call the click method of the clicked item. (some casting may be needed)
Actually there is no way to use a ListViewItem. You have to use the ListView itself. By using the 'SelectedItems' property of the ListView you can access the selected ListViewItems.
One option is to override the ListViewItem class an implement the specific stuff in there. Then you can cast the selected item to the overridden one and perform the action.
I really don't understand the reason to do so instead of just using the regular ListView Click event, but if I were to do like you suggest I would assign an EventHandler delegate to the Tag property of each ListViewItem, then in the ListView Click event handler I would check if the ListViewItem.Tag <> null, and if so call the delegate.