I have a slider control and both the app and user will be making adjustments. The slider will trigger the ValueChanged event when the value changes, but I only want to trigger that if it was from the user ie, mouse or keyboard input changed it. I guess I could do it the hard way and check mouse and keyboard events and set a bool but I'd rather handle it all in one event if possible.
How do I check who changed the value?
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
if(wasUser) {
...
}
}
The quick and dirty way is to take advantage of the fact that event-handlers are called synchronously via property-setters - so set a field (accessible by all the handlers) to selectively ignore events if the current event was indirectly raised by a previous event-handler instead of direct user action.
Consider a textbox that can have its text set by the program, or typed-in by the user:
class MyWindow : Window {
private Boolean isIndirectEvent = false;
void Timer_Tick() {
this.isIndirectEvent = true;
this.TextBox.Text = "foo"; // TextBox_TextChanged will be invoked inside this property's setter
this.isIndirectEvent = false;
}
void TextBox_TextChanged(Object sender, EventArgs e) {
if( this.isIndirectEvent ) return;
this.InvokeTurboEncabulator();
}
}
(Of course, the "best" approach would be to use MVVM, Dependency Properties and Data Bindings which completely obviates this entire class of problems, but that requires making significant changes to your program, fwiw).
Related
I have a CheckBox that has it's Checked property bound to a bool value. During the CheckedChanged event, some logic runs which uses the bool property on the data source.
My problem is the first time the CheckBox gets checked by the user, the bound data source does not get updated. Subsequent updates work fine though.
Here's some sample code for testing the problem. Just create a blank form and add a CheckBox to it.
public partial class Form1 : Form
{
private bool _testBool;
public bool TestBool
{
get { return _testBool; }
set { _testBool = value; }
}
public Form1()
{
InitializeComponent();
checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool"));
checkBox1.CheckedChanged += new EventHandler(checkBox1_CheckedChanged);
}
void checkBox1_CheckedChanged(object sender, EventArgs e)
{
checkBox1.BindingContext[this].EndCurrentEdit();
Debug.WriteLine(TestBool.ToString());
}
}
The first time I check the box, the TestBool property remains at false, even though checkBox1.Checked is set to true. Subsequent changes do correctly update the TestBool property to match checkBox1.Checked though.
If I add a breakpoint to the CheckedChanged event and check out checkBox1.BindingContext[this].Bindings[0] from the immediate window, I can see that modified = false the first time it runs, which is probably why EndCurrentEdit() is not correctly updating the data source.
The same thing also occurs with using a TextBox and the TextChanged event, so this isn't limited to just CheckBox.Checked.
Why is this? And is there a generic common way of fixing the problem?
Edit: I know of a few workarounds so far, although none are ideal since they are not generic and need to be remembered everytime we want to use a Changed event.
setting the property on the datasource directly from the CheckedChanged event
finding the binding and calling WriteValue()
hooking up the bindings after the control has been loaded
I am more concerned with learning why this is happening, although if someone knows of a standard generic solution to prevent it from happening that does not rely on any special coding in the Changed event, I'd be happy with that too.
Controls usually want to go through their validation first before writing to the data source, so the writing of the value usually won't happen until you try to leave the control.
You can force the writing of the value yourself:
void checkBox1_CheckedChanged(object sender, EventArgs e) {
Binding b = checkBox1.DataBindings["Checked"];
if (b != null) {
b.WriteValue();
}
Debug.WriteLine(TestBool.ToString());
}
Apparently, the CheckedChanged event is too early in the process.
But you can leverage BindingComplete:
public partial class Form1 : Form
{
private Boolean _testBool;
public Boolean TestBool
{
get { return _testBool; }
set { _testBool = value; }
}
public Form1()
{
InitializeComponent();
checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool", true, DataSourceUpdateMode.OnPropertyChanged));
checkBox1.DataBindings[0].BindingComplete += Form1_BindingComplete;
}
private void Form1_BindingComplete(Object sender, BindingCompleteEventArgs e)
{
Debug.WriteLine("BindingComplete: " + TestBool.ToString());
}
}
Note that the event will fire at startup as the initial bind linkage occurs. You will have to deal with that possible unintended consequence, but otherwise, it works on the first click and every click.
Also note that the true (format) is required in the Binding constructor to make the event fire.
The closest I can find for an explanation to this behavior is this 3rd party explanation
Basically, this is an issue of timing. The way binding works in DotNet
is actually very simple. There's no magic in the DotNet framework that
tells the BindingManager when something changes. What it does is, when
you bind to a property (such as CheckedValue) The BindingManager looks
for an event on the control called propertynameChanged (e.g.
"CheckedValueChanged"). This is the same event your code is hooking
into on your sample form.
When the control fires the event, the order in which the listeners
receive the event is arbitrary. There's no reliable way to tell
whether the BindingManager will get the event first or the Form will.
My CheckBox1_CheckChanged event is running before the BindingManager handles the changed event, so the data source hasn't been updated at this time.
My best guess as to why this only happens the first time is that the control isn't visible yet, so some code doesn't get run that should fix the order events get handled in. I've seen other posts about not being able to bind to non-visible items due to the handle not being created yet, and one answer states
Until the control is visible for the first time some back-end initialization never happens, and part of that initialization is enabling the data binding.
So I suspect that this is somehow related.
I can verify that if I attach the Changed handler later on such as during the Load event, it works as I would expect.
public partial class Form1 : Form
{
private bool _testBool;
public bool TestBool
{
get { return _testBool; }
set { _testBool = value; }
}
public Form1()
{
InitializeComponent();
checkBox1.DataBindings.Add(new Binding("Checked", this, "TestBool"));
Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
checkBox1.CheckedChanged += new EventHandler(checkBox1_CheckedChanged);
}
void checkBox1_CheckedChanged(object sender, EventArgs e)
{
// Not needed anymore
//checkBox1.BindingContext[this].EndCurrentEdit();
Debug.WriteLine(TestBool.ToString());
}
}
I was wondering about this problem for a while, but couldn't really come up with a solution. I have 2 different event handlers calling each other recursively. As soon as event A is fired, it triggers event B which triggers event A again and so on...
Basically I want to be able to select text in a RichTextBox and show the corresponding font size in a combo box. When I choose a different font size from the ComboBox, I want it's value to be applied to the selected text.
The 2 events are:
1) The selection changed event of text inside a RichTextBox:
private void MyRTB_SelectionChanged(object sender, RoutedEventArgs e)
{
//Get the font size of selected text and select the concurrent size from the ComboBox.
}
2) The selected index changed event of a Combobox:
private void CmbFont_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Apply the chosen font size to the currently selected text of the RichTextBox.
}
What would be the best solution to make sure they each only "do their thing" and do not fire the other event in doing so?
Sometimes changing a property of a control in code fires an event unintentionally. Changing the data source of a ListBox or a ComboBox will fire the SelectedIndexChanged event, for example. Use a flag to handle this case
private bool _loading;
...
_loading = true;
// Fill the ComboBox or ListView here
_loading = false;
In the event handler do this
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (_loading) return;
...
}
Refactor your code so that A calls DoSomethingA() and B calls DoSomethingB(). This way, if you want A to do the functionality of B you can just call DoSomethingB() and not have any recursive calls.
Just use a bool (maybe called dontFireA) and set it in A just before calling B
notifying properties (used in order to enable binding from WPF to non-WPF properies) use this technique:
public object MyProperty
{
get
{
return myField;
}
set
{
if (value != myField)
{
myField = value;
NotifyProperyChanged("MyProperty"); // raise event
}
}
}
The if (value != myField) condition prevents infinite recursion (stackoverflowexception).
In some cases (e.g. floating point numbers and inaccurate value transfers) if (Math.Abs(value - myField) > someConstant) is used instead to break the recursion.
Could you apply a similar technique to your problem?
If both events are on the same object or the owners have references to each other, you could also store a flag on each e.g.
private void OnEvent()
{
DoSomething();
}
private void DoSomething()
{
this.IsBusy = true;
// do work
// raise event
if (!other.IsBusy)
RaiseEvent();
}
I am going to make the educated guess that you are not raising Event A or Event B yourself; let's say Event A is the TextBox1.TextChanged event and Event B is the TextBox2.TextChanged event, and they have handlers like:
public void Textbox1_TextChanged(object sender, EventArgs e)
{
...
TextBox2.Text = someString;
}
public void Textbox2_TextChanged(object sender, EventArgs e)
{
...
TextBox1.Text = someOtherString;
}
In this case, the handlers are each going to raise the other textbox's TextChanged event by virtue of changing the text, leading to infinite recursion.
The first thing you can do, if you want both to run once and once only, is to mark that they're already running (changing the text of the other textbox results in that textbox's event handler running within the same call stack:
public void Textbox1_TextChanged(object sender, EventArgs e)
{
if(handler1Running) return; //the second time through we exit immediately
handler1Running = true;
...
TextBox2.Text = "Something"; //the other event handler is invoked immediately
handler1Running = false;
}
public void Textbox2_TextChanged(object sender, EventArgs e)
{
if(handler2Running) return; //the second time through we exit immediately
handler2Running = true;
...
TextBox1.Text = "Something Else"; //the other event handler is invoked immediately
handler2Running = false;
}
Now, the deepest it will go is three levels; 1's handler invokes 2's handler which invokes 1's handler again, which sees that 1's handler is already running and quits before doing anything that would deepen the recursion. Same thing if you start by changing TextBox2.
The other thing you can do is make sure you aren't trying to set the textbox to the same value that's already there. Changing from one string reference to another, even if both references are the same string value, will fire the TextChanged event. If the recursion must continue naturally but will reach a steady state, this is actually the first thing to try:
public void Textbox1_TextChanged(object sender, EventArgs e)
{
StringBuilder builder = new StringBuilder();
... //build string
//now, even though the builder's ToString will produce a different reference,
//we're making sure we don't unnecessarily change the text.
if(builder.ToString != TextBox2.Text)
TextBox2.Text = builder.ToString();
}
public void Textbox2_TextChanged(object sender, EventArgs e)
{
StringBuilder builder = new StringBuilder();
... //build string
//now, even though the builder's ToString will produce a different reference,
//we're making sure we don't unnecessarily change the text.
if(builder.ToString != TextBox1.Text)
TextBox1.Text = builder.ToString();
}
I have a simple TextBox that is empty in the beginning. I have a simple event, _TextChanged, to know when the user changed anything in that TextBox. However, the event fires if I do anything with it myself from within code. Like setting textbox.Text = "Test"; or similar.
private void textNazwa_TextChanged(object sender, EventArgs e) {
changesToClient = true;
}
How do I make the event only fire on user interaction and not code changes?
I've been using this process, and it seems to work well. If the event fires and the focus is not in the textbox, then I ignore the request, so when I set the text the focus is elsewhere, but when the user is typing in the textbox, it has the focus, so I acknowledge the changes.
private void textNazwa_TextCanged(object sender, EventArgs e)
{
if ( !textNazwa.Focused)
return;
}
The event itself does not make a distinction between text entered via user input and text changed via code. You'll have to set a flag yourself that tells your code to ignore the event. For example,
private bool ignoreTextChanged;
private void textNazwa_TextCanged(object sender, EventArgs e)
{
if (ignoreTextChanged) return;
}
Then use this to set the text instead of just calling Text = "...";:
private void SetTextboxText(string text)
{
ignoreTextChanged = true;
textNazwa.Text = text;
ignoreTextChanged = false;
}
Judging by your comment to another answer, it sounds like you have quite a number of textboxes. In that case, you could modify the function in this way:
private void SetTextBoxText(TextBox box, string text)
{
ignoreTextChanged = true;
box.Text = text;
ignoreTextChanged = false;
}
Then call it like this:
SetTextBoxText(textNazwa, "foo");
This would accomplish the same thing as just doing textNazwa.Text = "foo", but will set the flag letting your event handler know to ignore the event.
Well you can't not really anyway. What you can do is remove the handler before you make your change and add it back after you've made the change.
e.g.
textNazwa.TextChanged -= textNazwa_TextChanged;
textbox.Text = "Test";
textNazwa.TextChanged += textNazwa_TextChanged;
If your method has the same scope as where you're changing the value of the text box as the textNazwa_TextChanged (e.g. both are in a form) you could set a flag instead or if that's not oopsy enough for you, you could use Chain Of Responsiblity to determine if the textNazwa_TextChanged method should be called
I suggest you use Bindings for your TextBox with a presenter that has properties, so if you need to change your values in the code (for testing for example) you don't to fire events or change to UI code. The only thing you need to do is to set a value on your Presenter.
public class Presenter : INotifyPropertyChanged
{
public string MyTextValue { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
/// Create a method here that raises the event that you call from your setters..
}
And then in you Windows Forms code you need a bindingSource set to your Presenter, and add a Binding to you textBoxes:
EDIT
private BindingSource myPresenterSource ;
this.myPresenterSource = new System.Windows.Forms.BindingSource(this.components);
// Some time later in the
((System.ComponentModel.ISupportInitialize)(this.myPresenterSource )).BeginInit();
// you set the DataSource of your BindingSource
// m_SettingsBindingSource
//
this.myPresenterSource .DataSource = typeof(Presenter );
// and when you create your TextBox you do this :
this.YourTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text",
this.myPresenterSource, "MyTextValue", true,
System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
Then in your InitializeComponent you set the source like this :
myPresenterSource.DataSource = new Presenter();
Check more resources by looking for how to implement the Movel-View-Presenter (MVP) in Windows Forms.
I'm new to C# and Windows Form but if I have a radiobutton and I call radiobutton1.Checked=true, is there a way for it to not fire the CheckedChange event? I want to distinguish between the user clicking on the radiobutton and me setting the radiobutton programmatically. Is this possible?
Stop trying to defeat the design of the CheckedChanged event. It's specifically supposed to include programmatic changes.
If you want user-triggered changes and not programmatic changes, use the Click event instead. (You may be thinking that you don't want to restrict yourself to mouse clicks, don't worry, there's a MouseClick event for that, Click includes keyboard changes as well.)
Here's a straightforward method of using the event when you feel like it.
private bool SuppressRadioButton1Event { get; set; }
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
if (!this.SuppressRadioButton1Event)
{
MessageBox.Show("Not suppressed!");
}
}
private void button1_Click(object sender, EventArgs e)
{
this.SetRadioButton1(false);
}
private void SetRadioButton1(bool checkedOn)
{
this.SuppressRadioButton1Event = true;
radioButton1.Checked = checkedOn;
this.SuppressRadioButton1Event = false;
}
A very easy way:
public void radio_OnCheckChanged(object sender, EventArgs e)
{
RadioButton r = sender as RadioButton;
bool isUserChange = r.Tag.Equals(1);
if (isUserChange) blabla
else blabla
r.Tag = null;
}
public void MyMethod()
{
radio1.Tag = 1;
radio.Checked = true;
}
You can use any kind of flag which users can't do by their clicking.But you can do via your code.
Why should your code care who checked the radiobutton?
EDIT: There are ways around this (subclass, flag), but don't. The only "legit" reason I can think of for wanting this is to prevent some side-effect from happening when the value is initially (programatically) displayed, and even that is suspect. Rethink the side-effect, does it really belong on the change-event, or the commit?
More info one why/what would help. On the surface, this looks like a design error.
One (hackish) way to do it would be to subclass RadioButton and override the OnCheckChanged virtual method, suppressing the event if the Checked property has been set programmatically.
However, since radio-buttons belong to a group, the event always fires in pairs (oen for the uncheck, one for the check). You will therefore want to suppress the event for the entire group when you choose the selected button programmatically. Here's an example implementation:
public class CustomRadioButton : RadioButton
{
private bool _suppressCheckedEvent;
public void SetChecked(bool value, bool suppressCheckedEvent)
{
if (!suppressCheckedEvent)
Checked = value;
else
{
SetSupressModeForGroup(true);
Checked = value;
SetSupressModeForGroup(false);
}
}
private void SetSupressModeForGroup(bool suppressCheckedEvent)
{
foreach (var crb in Parent.Controls.OfType<CustomRadioButton>())
crb._suppressCheckedEvent = suppressCheckedEvent;
}
protected override void OnCheckedChanged(EventArgs e)
{
if (!_suppressCheckedEvent)
base.OnCheckedChanged(e);
}
}
In this implementation, changing the checked-state through the Checked property will always fire the event. When you call the SetChecked method, you have the choice to suppress the event.
You could try to attach the event programmatically. Based on my application configuration I check several radio buttons but I don't want to fire events.
To attach an event programmatically:
chbOptionX.CheckedChanged += new System.EventHandler(this.chbShowStockBySizeAndColor_CheckedChanged);
I am writing an IM program, and I have the method to make a form flash and stop flashing... question is, how do I implement it?
When a message arrives, I can set the window flashing, but I need to make sure it doesn't have focus. Checking the focued method always seems to return false and so it flashes even when the form is open.
Also, which event to I need to handle to stop it flashing? When the user clicks the form to make it maximise, or switches focus to the form, I need a way of stopping it.
What's the best way?
You can handle the Activated and Deactivate events of your Form, and use them to change a Form-level boolean that will tell your code whether your form has the focus or not, like this:
private bool _IsActivated = false;
private void Form1_Activated(object sender, EventArgs e)
{
_IsActivated = true;
// turn off flashing, if necessary
}
private void Form1_Deactivate(object sender, EventArgs e)
{
_IsActivated = false;
}
When a message arrives, you check _IsActivated to determine if your Form is already the active window, and turn on flashing if it isn't. In the Activated event, you would turn off the flashing if it's on.
The Focused property of your form will always return false if it has any controls on it. This property refers to whether the control in question (the form, in this case) has the focus within your application's form, not whether the application itself has the focus within Windows.
Checking if the form is minimized or not:
if (this.WindowState == FormWindowState.Minimized)
{
MakeFormFlash();
}
else
{
MakeFormStopFlash();
}
Event to trigger when the form is activated by user or code:
this.Activated += new EventHandler(Form_Activated);
Well Focused should be the property to check, so you need to try and work out why that is always returning false.
As for what event to listen to, probably the GotFocus event, though that may not work until you can work out what is wrong with the Focused property.
There are a number of ways you can handle this. Probably the easiest would be to have a flag that you set whenever the form is flashing so this can be reset on re-activation of the form e.g.
Code for base IM window form
private bool IsFlashing;
....
// Code for IM windows
public void OnActivate(EventArgs e)
{
if (IsFlashing)
{
// stop flash
IsFlashing = false;
}
}
public void Flash()
{
// make flash
IsFlashing = true;
}
Then wherever you do your code to handle the new message you would just need to check that the particular conversation window (if you handle multiple ones) that the message is directed at is the current active one:
public void OnNewMessage(AMessage msg)
{
Form convoWindow = FindConvoWindow(msg.Sender);
if (Form.ActiveForm == convoWindow)
{
// update the conversation text
}
else
{
convoWindow.Flash();
}
}