I've been asked to allow the user to (try to) change the selected value in the combobox event when its not valid. When he does so, I have to show an alert, and revert the SelectedItem of the combobox to its previous value.
I have something like:
private void cmbCountry_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Country selectedCountry = ((ComboBox)sender).SelectedItem as Country;
if (e.RemovedItems.Count > 0 && e.AddedItems.Count > 0 && !canChange)
{
e.Handled = true;
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
return;
}
// Do stuff...
}
The problem is, although I set the e.Handled = true; The SelectionChanged event keeps firing giving me a StackOverflowException...
I guess its a common problem...
Do I have an elegant way of solving this?
After you checked that your overall logic is correct as suggested by #nvoigt you may use this as a brute force trick:
if (e.RemovedItems.Count > 0 && e.AddedItems.Count > 0 && canChange)
{
e.Handled = true;
canChange = false; // avoid infinite recursion
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
canChange = true; // reset to original value
return;
}
This way the handler code is not called again from within the handler code.
If you change your SelectedItem in the ChangeHandler for the SelectedItem, you have an infinite recursion. The Handled flag only says this change was handled. For the new change you created, this function is called again.
The question is why is your change triggering your logic again? Shouldn't your change be the valid change? You will need to debug your program. Set a breakpoint and step through your method until you know what's wrong when you hit it a second time.
Related
I am developing a simple Universal Windows App using C#. I have a RichEditBox and I found a weird behavior when using Control+I key combination, which for some reason inserts a Tab (is that expected?). Because I wanted the combination of keys to toggle Italic font style, I thought the best way was through the KeyDown event.
So, this is my code:
private void richbox_KeyDown(object sender, KeyRoutedEventArgs e)
{
System.Diagnostics.Debug.Write("\nKeyDown : " + e.Key.ToString());
if (e.Key == VirtualKey.Tab)
{
richbox.Document.Selection.TypeText("\t");
e.Handled = true;
}
else if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) == Windows.UI.Core.CoreVirtualKeyStates.Down)
{
//If Control is pressed down, check if current key is B,I,U...
System.Diagnostics.Debug.Write(" => Control is down!");
switch (e.OriginalKey)
{
case VirtualKey.B:
toogleBold();
e.Handled = true;
break;
case VirtualKey.I:
e.Handled = true;
toogleItalic();
break;
case VirtualKey.U:
toogleUnderline();
e.Handled = true;
break;
}
}
}
My problem is, the condition on the Else If is not always true when I press the Control key. I would like to understand why and what could I do to fix it.
If I run the code and I press the control key a few times, this is the output:
KeyDown : Control => Control is down!
KeyDown : Control
KeyDown : Control => Control is down!
KeyDown : Control
...
Thanks in advance :)
I tried your code and used debugger output to see what the actual state of Ctrl is in those situations:
var state = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control);
Debug.WriteLine(state);
What I found out is that the second time you press the key, its state is not Down, but Down|Locked, more specifically Windows.UI.Core.CoreVirtualKeyStates.Down | Windows.UI.Core.CoreVirtualKeyStates.Locked. It turns out CoreVirtualKeyStates is a flag enum and it can have multiple values at the same time. In this case you are comparing with == which means you don't get a match. You can first use the HasFlag method or bitwise AND (&) to get the right value out and then compare and you will be good to go!
That means either this:
else if ( Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).
HasFlag( CoreVirtualKeyStates.Down ) )
{
//rest of the code
}
Or this:
else if (
( Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) &
Windows.UI.Core.CoreVirtualKeyStates.Down )
== CoreVirtualKeyStates.Down )
{
//rest of the code
}
Why this simple code causes fallowing error :
Cannot evaluate expression because the current thread is in a stack overflow state.
private void barButtonPanelVisibleCheck_CheckedChanged(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
{
switch (barButtonPanelVisibleCheck.Checked)
{
case true:
this.navBarControl.Visible = false;
this.barButtonPanelVisibleCheck.Checked = false;
break;
case false:
this.navBarControl.Visible = true;
this.barButtonPanelVisibleCheck.Checked = true;
break;
}
//or
if (barButtonPanelVisibleCheck.Checked == true)
{
this.navBarControl.Visible = false;
this.barButtonPanelVisibleCheck.Checked = false;
}
else
{
this.navBarControl.Visible = true;
this.barButtonPanelVisibleCheck.Checked = true;
}
}
You are changing Checked from within the Checked handler: Checked value is set, so the handler is called, which sets Checked value etc. and you have an infinite loop.
Since you are trying to change the checked state of your checkbox while you are inside the CheckedChanged event, you raise another CheckedChanged event, and this starts an infinite loop that consume the stack memory until you reach the StackOverflow exception.
Try to stop the recursion on your CheckedChanged event with
private void barButtonPanelVisibleCheck_CheckedChanged(object sender,
DevExpress.XtraBars.ItemClickEventArgs e)
{
try
{
this.barButtonPanelVisibleCheck.CheckedChanged -=
barButtonPanelVisibleCheck_CheckedChanged;
... do your checked changed here
}
finally
{
this.barButtonPanelVisibleCheck.CheckedChanged +=
barButtonPanelVisibleCheck_CheckedChanged;
}
}
Disconnecting the Event handler allows to change the checked state without reentry the event handler, after that, reconnect the event. Probably for this scenarion there is no need to use a try/finally but using finally will ensure that the event is always reconnected in case your code fails with an exception.
When checked state of the control is changed, method barButtonPanelVisibleCheck_CheckedChanged is called. In that method you change the Checked property of that control, which causes call to method barButtonPanelVisibleCheck_CheckedChanged. In that method you change the Checked property of that control.
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();
}
}
I have a DateTimePicker with ShowCheckBox = true on my winforms app. If I do this in the forms constructor:
DateFrom.Checked = true;
DateFrom.Value = DateTime.Today.AddDays(-7);
Then set DateFrom.Checked = false; in the FormShown event, it does what I would like, the text in the control defaults to 7 days before today, and the checkbox is unchecked.
If I try to only set the Value, the text stays as today. If I reset Checked = false anytime before the FormShown event, the text stays as today.
Now I've moved this code to a user control, so to use the same "hack" will require even more hacking, so at this point I'm hoping someone has an easier method. Maybe just another property I can set besides from Value that actually works? :)
I tried this also:
DateFrom.Text = DateTime.Today.ToString(DateFrom.CustomFormat);
Instead of setting the value, or in addition to it, to no avail.
Typical, I tried for hours before posting my question, then right after I thought it might somehow be related to the creation of the window handle. So I came up with this solution. Will still be happy to have something better, but this doesn't seem to bad if I have to stay with this:
DateFrom.Checked = true;
DateFrom.Value = DateTime.Today.AddDays(-7);
if (DateFrom.Handle == null)
System.Threading.Thread.Sleep(0);
DateFrom.Checked = false;
Checking Handle forces the window handle to be created, so then I'm able to uncheck the control without it defaulting to today's date for the text when the window handle is created later. I just use Sleep(0) as a trick to make sure the compiler doesn't optimize the code and compile it out all together (not sure if that would even happen, but like to be sure, and condition shouldn't always be false so should never Sleep(0) anyway).
It might also be that the control is simply not redrawing properly, particularly if you have it as a usercontrol. You might try calling Invalidate() on the control after setting the Value to see if that's the problem.
I changed your self answer and made it like this:
public ProcessFailureForm()
{
InitializeComponent();
//Blah blah blah
dtFrom.HandleCreated += delegate //if you need sender or EventArgs use: delegate(object sender, EventArgs e)
{
dtFrom.Checked = true;
dtFrom.Value = DateTime.Today.AddDays(-7);
dtFrom.Checked = false;
};
}
Update:
Actually first i think like you, but after doing this, i tried and find out that the Checked state won't affect the process... so it can be reduced to:
public ProcessFailureForm()
{
InitializeComponent();
//Blah blah blah
dtFrom.HandleCreated += delegate //if you need sender or EventArgs use: delegate(object sender, EventArgs e)
{
dtFrom.Value = DateTime.Today.AddDays(-7);
};
}
I have a method that disables all the butttons on my window.
But i can't seem to get the type of Button to match it to the Resource collection
I'm using Expression Blend 3 with a c# code-behind
void DisableButtons()
{
for(int i = 0; i>= this.Resources.Count -1; i ++)
{
if (this.Resources[i].GetType() == typeof(Button))
{
Button btn = (Button)this.Resources[i];
btn.IsEnabled = false;
}
}
}
Update
Thanks for the answers!
Ok The loop is working but my code is incorrect.
this.Resources
Does not seem to include my buttons! This could be an Blend thing?
So Yeah.
I ended up doing it manually. Cause I'm hasty and there isn't a short easy solution. Thanks for all the input though!
void DisableButtons()
{
for(int i = 0; i < Resources.Count; i ++)
{
var btn = Resources[i] as Button;
if(btn != null)
{
btn.IsEnabled = false;
}
}
}
Easy way to implement it is use foreach instruction with LINQ query, but this way need more resuources whan easy for.
void DisableButtons()
{
foreach(var button in Resources.OfType<Button>())
{
button.IsEnabled = false;
}
}
It is possible I misunderstood something, but you're trying to find Buttons CONTAINED in your window in the RESOURCES of your window? Because those two things are different things alltogether.
If that is the case, either try setting this.IsEnabled = false (but that disables other things, not just buttons), or traverse the logical tree (or visual tree if silverlight) with LogicalTreeHelper/VisualTreeHelper, although that is a VERY expensive method.
Manual workaround would be to give names to all your buttons, make a list of them in codebehind and iterate that list.
However the best would be to create a boolean property called AreButtonsEnabled in your ViewModel (if you're not using MVVM than simply on the control itself - but make it DependencyProperty) and bind all your button's IsEnabled property to them! And then in codebehind simply set that boolean to false ... and magic ensues.
If this is not your case then sorry I wasted your time.
what about this?
if (this.Resources[i].GetType() == typeof(Button)))
or even better
if (this.Resources[i] is Button))
What about that?
if (this.Resources[i] is Button)
That way you can get anything that inherits from Button.
It looks like your loop statement is wrong. i>= this.Resources.Count -1 should be i <= this.Resources.Count - 1; It'll never get into your loop.
Also, this is just a style thing, but I'd rewrite it as:
for(int i = 0; i < Resources.Count; i ++)
{
Button btn = Resources[i] as Button; // btn will be null if not a Button
if( btn != null )
{
btn.IsEnabled = false;
}
}
I use a simple method.
First you create a bool.
bool enableButtons = true;
Now I add a timer to the form which is active all the time.
private void timer1_Tick(object sender, EventArgs e)
{
if (enableButtons = false)
{
button1.Enabled = false;
button2.Enabled = false;
}
else
{
button1.Enabled = true;
button2.Enabled = true;
}
}
So whenever I want to disable the buttons, I just change enableButtons to false.