Background:
In my winforms form, I have a Checked ListView and a "master" checkbox called checkBoxAll.
The behaviour of the master is as follows:
If the master is checked or unchecked, all ListViewItems must change accordingly.
If the user unchecks a ListViewItem, the master must change accordingly.
If the user checks a ListViewItem, and all other ListViewItems are checked aswell, the master must change accordingly.
I have written the following code to mimic this behaviour:
private bool byProgram = false; //Flag to determine the caller of the code. True for program, false for user.
private void checkBoxAll_CheckedChanged(object sender, EventArgs e)
{
//Check if the user raised this event.
if (!byProgram)
{
//Event was raised by user!
//If checkBoxAll is checked, all listviewitems must be checked too and vice versa.
//Check if there are any items to (un)check.
if (myListView.Items.Count > 0)
{
byProgram = true; //Raise flag.
//(Un)check every item.
foreach (ListViewItem lvi in myListView.Items)
{
lvi.Checked = checkBoxAll.Checked;
}
byProgram = false; //Lower flag.
}
}
}
private void myListView_ItemChecked(object sender, ItemCheckedEventArgs e)
{
//Get the appropiate ListView that raised this event
var listView = sender as ListView;
//Check if the user raised this event.
if (!byProgram)
{
//Event was raised by user!
//If all items are checked, set checkBoxAll checked, else: uncheck him!
bool allChecked = true; //This boolean will be used to set the value of checkBoxAll
//This event was raised by an ListViewItem so we don't have to check if any exist.
//Check all items untill one is not checked.
foreach (ListViewItem lvi in listView.Items)
{
allChecked = lvi.Checked;
if (!allChecked) break;
}
byProgram = true; //Raise flag.
//Set the checkBoxAll according to the value determined for allChecked.
checkBoxAll.Checked = allChecked;
byProgram = false; //Lower flag.
}
}
In this example, I use a flag (byProgram) to make sure an event was caused by the user or not, thereby preventing an infinite loop (one event can fire another, which can fire the first one again etc. etc.). IMHO, this is a hacky solution.
I searched around but I couldn't find a MSDN documented method to determine if an User Control Event was directly fired thanks to the user. Which strikes me as odd (again, IMHO).
I know that the FormClosingEventArgs has a field which we can use to determine if the user is closing the form or not. But as far as I know, that is the only EventArg that provides this kind of functionality...
So in summary:
Is there a way (other than my example) to determine if an event was fired directly by the user?
Please note: I don't mean the sender of an event! It won't matter if I code someCheckBox.Checked = true; or manually set someCheckBox, the sender of the event will always be someCheckBox. I want to find out if it is possible to determine whether it was through the user (click) or by the program (.Checked = true).
Aaand also: 30% of the time it took to write this question was to formulate the question and the title correctly. Still not sure if it is a 100% clear so please edit if you think you can do better :)
No, there's no practical way to determine whether the change came from GUI or was done by program (in fact, you could analyze the callstack - but that's not recommended because it's very slow and error-prone).
BTW, there's one other thing you could do instead of setting byProgram. You could remove and add the event handler prior or after, respectively, change your controls:
checkBoxAll.CheckedChanged -= checkBoxAll_CheckedChanged;
// do something
checkBoxAll.CheckedChanged += checkBoxAll_CheckedChanged;
Instead of using the changed event, you could use the clicked event to cascade the change through to the relevant controls. This would be in response to a user click, and not the value being changed programatically.
This is something I come across quite a lot and what I tend to try do is not split it between user interaction vs program interaction - I use more generic code i.e. the UI is being updated and doesn't require any events to be handled. I usually package this up through BeginUpdate/EndUpdate methods e.g.
private int updates = 0;
public bool Updating { get { return updates > 0; } }
public void BeginUpdate()
{
updates++;
}
public void EndUpdate()
{
updates--;
}
public void IndividualCheckBoxChanged(...)
{
if (!Updating)
{
// run code
}
}
public void CheckAllChanged(...)
{
BeginUpdate();
try
{
// run code
}
finally
{
EndUpdate();
}
}
Related
On a Form I have two controls, A and B. Each one sends an event when its value changes. The Form handles A's ValueChanged event by setting B to some value, and B's ValueChanged by setting A's value.
Do I need to do anything special to prevent an infinite loop, where the user changes A, sending a ValueChanged event which causes B to update, which now sends it ValueChanged event, causing A to update...
I know some GUI toolkits, such as Qt, have logic built into the infrastructure to prevent loops like that. (See the "Enter Your Age" example in Ch. 1 of the Blanchette & Summerfield book on Qt4.) Some older toolkits required the programmer to define and manage a flag to detect recursion. For WinForms, I haven't read a definitive statement anywhere on this.
For a concrete example, suppose A and B are NumericUpDown controls to show temperature in Fahrenheit and Celsius. When the user changes one, the other updates to show the corresponding temperature in the other system.
In my experience, the loop usually ends because the values stop actually changing. Your example falls into this case. Consider the following scenario:
User changes the Fahrenheit to 32
Events update the Celsius to 0
Events set the Fahrenheit to 32
Nothing further happens because Fahrenheit did not change
This is usually implemented in the properties by putting a check at the top of the setter to not raise the changed event when the new value is the same as the current value.
The way in which I've managed to prevent this problem when implementing custom controls, is by raising events only when the value actually does get changed, like this.
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnTextChanged();
}
}
}
One could argue that not checking whether the value is actually different before firing the event is bad code.
I don't recall ever experiencing these issues with Windows Forms controls, so the ones I've been using must be doing this check correctly.
The events will not loop in the NumericUpDown example that you have provided. The ValueChanged event of the NumericUpDown will only get fired when the value changes i.e. If the value of a NumericUpDown is 5 and in code you again set it to 5, no event will be fired. This behavior of the event stops it from looping when you have two NumericUpDown controls.
Now suppose you have two NumericUpDown controls A & B.
A was changed by the user, it fires an event
On the event fired by A, you calculate and set the value of B. B detects a value change and fires an event
On the event fired by B, you calculate and set the value of A. However this value would be the same as the original value and Windows will not fire an event of ValueChanged.
So in the case of Windows Form Controls, the Framework manages it for you, if you want to achieve this for your own classes you follow a similar principle. On the setter of a value, check if the new value is different from the old value. Only if it differs fire an event.
I think you should detach B event before set B in A event. So B event never fire when you set B in A event. you should same thing for B event. You can break loop.
Sorry for my English. I hope it help you.
There isn't really any standard. As a user, you should handle controls that raise useless events, and controls that do not. Even for .NET's own controls, documentation is lacking on this aspect so you're left just trying it.
Using a flag to detect recursion is possible, but nicer IMO is to change control.Value = newvalue; to if (control.Value != newvalue) control.Value = newvalue;.
That said, if you write your own controls, please do not raise useless events, and please clearly document that fact.
To answer your question I have built a simple test with the code below.
public partial class Form1 : Form
{
Random r = new Random();
public Form1()
{
InitializeComponent();
}
private void Form1_Shown(object sender, EventArgs e)
{
// This triggers the infinite loop between the two controls
numericUpDown1.Value = 10;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
int cur = (int)numericUpDown2.Value;
int r1 = r.Next(1, 100);
while (r1 == cur)
r1 = r.Next(1, 100);
Console.WriteLine("Random in NUM1=" + r1);
numericUpDown2.Value = r1;
}
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
int cur = (int)numericUpDown1.Value;
int r1 = r.Next(1, 100);
while (r1 == cur)
r1 = r.Next(1, 100);
Console.WriteLine("Random in NUM2=" + r1);
numericUpDown1.Value = r1;
}
}
The form has only two NumericUpDown initialized with values 1 and 2.
As you could test in Visual Studio 2013 there is nothing to prevent an infinite recursion in the case in which you really manage to generate different numbers at every ValueChanged event.
To prevent the infinite loop you could use the usual technique. Declare a global form variable
bool changing = false;
Inside both events add this code
try
{
if (changing) return;
changing = true;
.... event code ....
}
finally
{
changing = false;
}
I have a simple application that reverses any text typed to it in another textbox. The catch is, you can modify either textbox and the changes will be (literally) reflected in the other.
I wrote this code, believing for it to cause problems.
private void realText_TextChanged(object sender, EventArgs e)
{
mirrorText.Text = mirror(realText.Text);
}
private void mirrorText_TextChanged(object sender, EventArgs e)
{
realText.Text = mirror(mirrorText.Text);
}
private string mirror(string text)
{
return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
}
I then tried it out, believing that it would cause an infinite loop (realText changes mirrorText, another event happens, mirrorText changes realText, etc). However, nothing except the intended behavior happened.
I'm of course happy about this, I could just leave it here. Or could I?
I'm quite sure the TextChanged event is supposed to be fired whenever Text is changed. Is this intended behavior of some error protection in the events, or was I just lucky? Can this code misbehave on another computer, with other build settings, etc? It can be easily fixed:
private void realText_TextChanged(object sender, EventArgs e)
{
if (realText.Focused)
{
mirrorText.Text = Mirror(realText.Text);
}
}
I'll probably do it anyway to be safe, but is it required to check this? (I'm not even going to ask if it's recommended.)
Per the comments, and as already answered, the TextChanged event is not getting raised when you set the Text property to the value it already has.
It's not clear whether this is something you can safely rely upon. It is a sensible optimisation, and I would be very surprised if future versions of .NET Framework drop it, but I cannot speak for older versions, nor for third-party implementations (Mono).
To be absolutely safe, I would not use the Focused check you put in your question. I would do exactly what the Text setter does now.
private void realText_TextChanged(object sender, EventArgs e)
{
var newMirrorText = Mirror(realText.Text);
if (mirrorText.Text != newMirrorText)
mirrorText.Text = newMirrorText;
}
This has the same advantage of preventing infinite recursion, but plays more nicely with other code you may put in your form that changes the text as a result of some other event.
The reason it doesn't cause a loop is that it checks whether the Text property actually changed, i.e. if the new value does not equal the old value. In your case the mirror function happens to reverse itself, which leads to the same text after two passes.
It's pretty easy to check.
First, replace both textbox controls with
class T : TextBox
{
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
}
Second, set the breakpoint on setter. Add these expressions to the Watch window:
Name
Text
value
Third, launch the app, copy '123' from somewhere and paste it to the first textbox. Here it goes:
1st break:
Name: "mirrorText"
Text: ""
value: "321"
2nd break:
Name: "realText"
Text: "123"
value: "123"
3rd... whoops, it does not breaks anymore. To detect why we had to go deeper. Look at referencesource: text box setter does nothing unusual, but TextBoxBase's one looks interesting:
set {
if (value != base.Text) { // Gotcha!
base.Text = value;
if (IsHandleCreated) {
// clear the modified flag
SendMessage(NativeMethods.EM_SETMODIFY, 0, 0);
}
}
}
So, as hvd already answered, the reason is the textbox does not raise TextChanged if old and new values are the same. I don't think the behavior will change, at least for winforms. But if you want more robust solution, here it is:
private void RunOnce(ref bool flag, Action callback)
{
if (!flag)
{
try
{
flag = true;
callback();
}
finally
{
flag = false;
}
}
}
private bool inMirror;
private void realText_TextChanged(object sender, EventArgs e)
{
RunOnce(ref inMirror, () =>
{
mirrorText.Text = mirror(realText.Text);
});
}
private void mirrorText_TextChanged(object sender, EventArgs e)
{
RunOnce(ref inMirror, () =>
{
realText.Text = mirror(mirrorText.Text);
});
}
private string mirror(string text)
{
return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
}
P.S. mirror() will fail on surrogate pairs. Here're some solutions.
If textbox has a Text, and we try to change it with the same Text, the TextChange event is not raising because new text is same as the previous.
In your code, the realText_TextChanged event reverses the text and changes the mirrorText with it.
The mirrorText_TextChanged event reverses the text and try to change the realText.
The realText has already this text and does not raises the realText_TextChanged event.
I use a binding source so that all my controls are bound to datasource. Like this:
var category = categoryRequest.Get(id);
bindingSource.DataSource = category;
This works fine.
I've also implemented INotifyPropertyChanged on the DTO classes (even though this should not be done), so that a change in the object's properties is reflected immediately in the corresponding controls. This also works.
However, if the user loads an object, changes some text in some controls and decides to close the form, I would like to determine if data has been changed and prompt a "Are you sure?" message.
Currently, the way I'm doing it is like this:
public static bool DataChanged(this Form form)
{
bool changed = false;
if (form == null)
return changed;
foreach (Control c in form.Controls)
{
switch (c.GetType().ToString())
{
case "TextBox":
changed = ((TextBox)c).Modified;
break;
//Other control types here...
}
if (changed)
break;
}
return changed;
}
But I don't think this is the best way to do it because:
Each control type needs to the added manually
Checking if lists have changed won't work
Is there a better way to achieve what I need?
Do you want to check it only once? Like before closing the window.. If you do you can
declare public static bool changed=false; in the form class and change its value to true from where you have implimented the INotifyPropertychanged.
you can display a messagebox anywhere in the form as follows.
if(changed)
{
if (MessageBox.Show("Are you sure?","some caption",MessageBoxButtons.YesNo)==DialogResult.Yes)
{
//Do this if user presses YES
}
}
I realize this is an older thread, but I would suggest a simple solution:
if (YourTextBox.Modified)
{
// Your code goes here.
}
I think it has been around since version 1.0. You will find further information here.
Just subscribe to the BindingSource's ListChanged event and set an IsDirty flag based on the event.
categoryBindingSource.ListChanged += new System.ComponentModel.ListChangedEventHandler(categoryBindingSource_ListChanged);
and set IsDirty = true in the event method...
void customerAccountBindingSource_ListChanged(object sender, system.ComponentModel.ListChangedEventArgs e)
{
if (e.ListChangedType == System.ComponentModel.ListChangedType.ItemChanged)
_isDirty = true;
}
I am working with a DataGridView, and I use the CellValueChanged event.
I dont want this event to be triggered when I change a cell value by the code. However, I want it to be triggered when the user edits it.
That's why I enclose my cell value change operations with the following code :
void changeCellOperation()
{
dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged);
...
cell.Value = myNewCellValue
...
dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged);
}
I ended to have several differents functions where my DataGridView cells are updated this way.
Because these functions are called from different places and can be nested, I cannot afford to keep this code as is to avoid event unwanted event reactivation.
So I ended up this way :
int valueChangedEventMask = 0;
void changeCellOperation()
{
valueChangedEventMask++;
...
cell.Value = myNewCellValue
...
valueChangedEventMask--;
}
void dgv_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (valueChangedEventMask > 0)
return
...
}
This works fine. Also when the calls are nested, including inside the event itself.
But the CellValueChanged event is now fired too many times for no reasons.
Because I often have to cope with this pattern, I am looking for a solution that can be applicable generally for Events in UIs, not only the DataGridView.
So my question is:
What is the best tip to mask UI Events correctly and avoid unnecessary Events fires ?
CellValueChanged is not an UI event, but a property changed event. That means you can not use it to distinguish user input from programmatic change. You can always use subscriber/unsucscribe or flag+/- or BeginEdit/EndEdit-similar technique, but maybe you have to find another (better) approach. To example, in case of checkbox you can use Click event instead of Changed, because (surprise!) it will tell you when the user click it and otherwise safely change value of Checked programmatically.
In case of DataGridView easiest would be to use Changed with some flag (which will be set when edit begins and reset when ends - see, CellBeginEdit/CellEndEdit ).
You could use CellEndEdit instead of CellValueChange. I don't know what your method dgv_CellValueChanged does, just be careful that CellEndEdit is fired every time you exit the edit mode for the cell, even if its value has not been changed. This means that you have to keep trace of the current values of your cells if you don't want the method to be executed when the value doesn't change.
I would avoid events related with the mouse such as CellClick because your users could use just the keyboard.
Anyway I usually avoid this kind of problems by separating the logic from the user interface, i.e. I write a separate class which is bound to the form. Take a look at MVVM (you can implement your own version in WinForms if you want) or the good old MVC.
I ended up mixing both solutions in a very simple one. I use a counter and I only hook/unhook the events I want to mask.
EventMask valueChangedEventMask;
// In the class constructor
valueChangedEventMask = new EventMask(
() => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); },
() => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); }
);
// The value change operation I want to hide from the event
void changeCellOperation()
{
valueChangedEventMask.Push();
...
cell.Value = myNewCellValue
...
valueChangedEventMask.Pop();
}
// The class
public class EventMask
{
Action hook;
Action unHook;
int count = 0;
public EventMask(Action hook, Action unHook)
{
this.hook = hook;
this.unHook = unHook;
}
public void Push()
{
count++;
if (count == 1)
unHook();
}
public void Pop()
{
count--;
if (count == 0)
hook();
}
}
Why does this never get called ?
propertyGrid.KeyDown += new KeyEventHandler(propertyGrid_KeyDown);
private void propertyGrid_KeyDown(object sender, KeyEventArgs e)
{
PoorLittleMethod(); //Never gets called
}
This seems to be the same for Mouse event
I'veread on some forums that PGrid is tricky on raising such events as it Inherits them from Control but does not really Raise them. is that true ? If yes, how to bypass that ?
EDIT 1:
As this seems to be "regular", I find it very light from MS not to specify this explicitely on the MSDN Reference of the propertyGrid class and leave events "as is" as if they were usable, whereas they are not. Tricky things like these are at least usually specified in "notes" inside the refs.
EDIT 2:
I am presently coding a workaround. I'll be posting it soon.
The PropertyGrid's KeyDown property is marked as Browsable(false) - presumably the conclusion we can take from this is that it is not supported in an of itself but is in fact present as a side-effect of its inheritance hierarchy.
Though, interestingly enough, its EditorBrowsable attribute (which is also a designer indicator, for Intellisense and the suchlike) is set as EditorBrowsableState.Advanced - where we would expect EditorBrowsableState.Never should the former presumption be true.
Some information from MSDN forums outlines the why of this situation:
From the tool UI Spy we can see the PropertyGrid is a just a panel and it consists of three Windows Controls. Our KeyDown event should be processed by the child control table.
The structure:
-"pane" "PropertyGrid"
--"pane" "Description Pane"
--"table" "Properties Window"
--"tool bar" "ToolBar"
The suggested solution (also provided in the MSDN link) to overcoming this is to use native system calls to retrieve window/control information, subclass NativeWindow and override the WndProc method to handle the events you like, KeyDown in this case.
You can override this from subclass of PropertyGrid to get some key info from windows message
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
CSharp PropertyGrid Events
// Property grid events can’t be easily subscribed to however there is way to get at the KeyUp event without impacting operation.
// Note: The KeyDown event can be subscribed to in the same manner but the propertygrid is NOT updated with the key presses.
// This code is added in hope it may help someone else solve the problem. It is not offered as a total solution.
// First define a class variable to indicate that events have been added.
private bool m_bPropertyGridEventsAdded = false;
public GlassInfoEntryPage(ViewBase view)
: base(view)
{
InitializeComponent();
// Subscribe to SelectedGridItemChanged
m_PropertyGrid.SelectedGridItemChanged += M_PropertyGrid_SelectedGridItemChanged;
}
// Now define a SelectedGridItemChanged Event Handler
private void M_PropertyGrid_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
int nXlocation;
int nYlocation;
PropertyGrid oPropertyGrid;
Control oControl;
if (m_bPropertyGridEventsAdded == false)
{
oPropertyGrid = (PropertyGrid)sender;
// Search the Property Grid for a PropertyGridView Control so events can be added to it
for (nXlocation = 0; nXlocation < oPropertyGrid.Width; nXlocation += 10)
{
for (nYlocation = 0; nYlocation < oPropertyGrid.Height; nYlocation += 10)
{
oControl = m_glassInfoPropertyGrid.GetChildAtPoint(new Point(nXlocation, nYlocation));
if (oControl != null)
{
if (oControl.GetType().ToString() == "System.Windows.Forms.PropertyGridInternal.PropertyGridView")
{
// Add Events here
oControl.Controls[1].KeyUp += MyCode_KeyUp;
m_bPropertyGridEventsAdded = true;
break;
}
}
}
if (m_bPropertyGridEventsAdded == true)
{
break;
}
}
}
}
// Handle the events
private void MyCode_KeyUp(object sender, KeyEventArgs e)
{
}