SizeChanged event being fired recursively - c#

I have a SizeChanged event in one of my windows. One of the user controls Width's is behaving interestingly, so I decided to staticly set the width of my window in a SizeChanged event. Problem is, when I set the size of the window in the size changed event, it fires another size changed event! I want the user to be able to resize the window, and then only have the event fire once. I have tried :
e.Handled = true;
As well as adding an event handler in the window constructor, and removing it in the size changed event. (This makes it only be able to fire once and won't ever fire again in the window's lifetime). Any ideas?

you should use a private bool and change its value when the size changed
bool _sizeChanged=false;
void handleResize(Object sender, EventArgs e)
{
if (_sizeChanged==false)
{
// do stuff
}
_sizeChanged=true;
}
But is is not enough, because you should change its value again somewhere else. if you do not change its value (for example to false somewhere else) it will never pass the 'if' condition again. So the question is, where you should change its value.
I think you can change the value at MouseButtonUp event, since resizing is done with the mouse.

You can use a boolean to determine whether or not to handle your event.
private bool m_handleResizeEvent;
private void HandleResize(object sender, EventArgs e)
{
if (m_handleResizeEvent)
{
m_handleResizeEvent = false;
// perform your resize here
m_handleResizeEvent = true;
}
}

Turns out it was the SizeToContent="Width" property in my Window's XAML that was causing the SizeChanged to be called multiple times. Removing this property fixed my issue and allowed me to resize the window without the event being fired multiple times. Thanks everyone else for your answers and input!

Related

Stop events in WPF

Is it possible to stop/suspend raised events in WPF? I want to stop events like SizeChanged, LocationChanged, MouseDown, when my window touches a screen side.
For now on I cannot modify window dimensions, because somehow other events overwrites the values.
EDIT I've discovered, that my problem is caused by raised events which overwrites my window object (i guess so), because when I've printed window dimensions in all the events, it occurred that after change other event causes setting original dimensions.
To unsubscribed from an event just type this in the place where you want to stop.
eg: SizeChanged;
this.SizeChanged -= MethodName;
AFAIK you cannot suspend firing of events. You can either unsubscribe using EventHandlerssimilar to subscribing them:
object.someEventHandler -= someMethodOrDelegate
or you can suppress the bubbling up/tunneling down of event based on special condition:
private void SomeEventHappened(object sender, RoutedEventArgs e)
{
if(someCondition)
e.Handled = true;
else
DoSomeWork();
}
As per your edit on question and your comments, as far as I understood you have events that change window dimensions, and these prevent you from resizing window manually. Moreover, your eventhandlers are not WPF originated (they use EventArgs instead of RoutedEventArgs). If I understood correctly, a solution may be having a class-scoped (or internal, based on your needs) boolean variable that holds whether to allow/overwrite dimensions or not:
bool allowManualResize = false;
And then when you are doing size modifications manually, you set this variable to true:
private void SomeManualSizingEventHandler(object sender, EventArgs e)
{
allowManualResize = true;
....
After these, you can check for this value in your events that override your new values:
private void SomeUpperLevelResizeHandler(object sender, EventArgs e)
{
// Check if resizing is allowed,
if(!allowManualResize)
{
// do your default things here
....
Of course you need to find a suitable location to set allowManualResize to false so that future firings are not suppressed.

FlatStyle.System raise strange MouseHover event

With the following code, when i move the mouse into the button and click it, the final value of result is 2 instead of 1.
static int result = 0;
private void button2_MouseHover(object sender, EventArgs e)
{
result++;
}
private void button2_MouseDown(object sender, MouseEventArgs e)
{
button2.FlatStyle = FlatStyle.System;
}
I had tried different setting:
Using MouseEnter instead of MouseHover give result = 1
Only changing of FlatStyle to System will trigger MouseHover one more time after button is clicked.
If the FlatStyle of button is originally FlatStyle.System, give result = 1;
If the FlatStyle is changed to others Style(popup, flat), give result = 1;
So i guess the problem lies with the FlatStyle set to FlatStyle.System, anyone can explains this to me?
This is a fairly common quirk in Winforms. You can visualize what's going with this code:
private void button1_MouseHover(object sender, EventArgs e) {
Console.WriteLine("{0:X}, {1}", button1.Handle, DateTime.Now.TimeOfDay);
result++;
}
Look in the Output window for the displayed diagnostic. On mine:
12780286, 03:04:10.7619696
12780286, 03:04:16.2935137
12780286, 03:04:20.1062178
12780286, 03:04:23.9970596
Click
12845822, 03:04:28.9191911
12845822, 03:04:32.3099880
Note how the Handle property value changed after the click. In other words, the physical Windows window changed. This happened because you changed the FlatStyle property. Winforms implements this by destroying the button control window and recreating it. If you look really close then you can actually see this, the button briefly flickers.
There are a few properties that are implemented like this, they are "heavy" properties that requires the window to be recreated since the style flags changed. Style flags that are passed in the native CreateWindowEx() call. A change in those style flags requires another call to CreateWindowEx().
This tends to have side-effects. Destroying the old window causes its state to be lost. Winforms tries to restore the state of the new window as best it can, but there are a few it cannot get to. The "window was hovered" state is one of them. Clearly the new window has that internal state flag set to false. Which is why you get the MouseHover event again. You'll need to work around this quirk.

Is there any way to detect a mouseclick outside a user control?

I'm creating a custom dropdown box, and I want to register when the mouse is clicked outside the dropdown box, in order to hide it. Is it possible to detect a click outside a control? or should I make some mechanism on the containing form and check for mouseclick when any dropdownbox is open?
So I finally understand that you only want it to close when the user clicks outside of it. In that case, the Leave event should work just fine... For some reason, I got the impression you wanted it to close whenever they moved the mouse outside of your custom dropdown. The Leave event is raised whenever your control loses the focus, and if the user clicks on something else, it will certainly lose focus as the thing they clicked on gains the focus.
The documentation also says that this event cascades up and down the control chain as necessary:
The Enter and Leave events are hierarchical and will cascade up and down the parent chain until the appropriate control is reached. For example, assume you have a Form with two GroupBox controls, and each GroupBox control has one TextBox control. When the caret is moved from one TextBox to the other, the Leave event is raised for the TextBox and GroupBox, and the Enter event is raised for the other GroupBox and TextBox.
Overriding your UserControl's OnLeave method is the best way to handle this:
protected override void OnLeave(EventArgs e)
{
// Call the base class
base.OnLeave(e);
// When this control loses the focus, close it
this.Hide();
}
And then for testing purposes, I created a form that shows the drop-down UserControl on command:
public partial class Form1 : Form
{
private UserControl1 customDropDown;
public Form1()
{
InitializeComponent();
// Create the user control
customDropDown = new UserControl1();
// Add it to the form's Controls collection
Controls.Add(customDropDown);
customDropDown.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
// Display the user control
customDropDown.Show();
customDropDown.BringToFront(); // display in front of other controls
customDropDown.Select(); // make sure it gets the focus
}
}
Everything works perfectly with the above code, except for one thing: if the user clicks on a blank area of the form, the UserControl doesn't close. Hmm, why not? Well, because the form itself doesn't want the focus. Only controls can get the focus, and we didn't click on a control. And because nothing else stole the focus, the Leave event never got raised, meaning that the UserControl didn't know it was supposed to close itself.
If you need the UserControl to close itself when the user clicks on a blank area in the form, you need some special case handling for that. Since you say that you're only concerned about clicks, you can just handle the Click event for the form, and set the focus to a different control:
protected override void OnClick(EventArgs e)
{
// Call the base class
base.OnClick(e);
// See if our custom drop-down is visible
if (customDropDown.Visible)
{
// Set the focus to a different control on the form,
// which will force the drop-down to close
this.SelectNextControl(customDropDown, true, true, true, true);
}
}
Yes, this last part feels like a hack. The better solution, as others have mentioned, is to use the SetCapture function to instruct Windows to capture the mouse over your UserControl's window. The control's Capture property provides an even simpler way to do the same thing.
Technically, you'll need to p/invoke SetCapture() in order to receive click events that happen outside of your control.
But in your case, handling the Leave event, as #Martin suggests, should be sufficient.
EDIT: While looking for an usage example for SetCapture(), I came across the Control.Capture property, of which I was not aware. Using that property means you won't have to p/invoke anything, which is always a good thing in my book.
So, you'll have to set Capture to true when showing the dropdown, then determine if the mouse pointer lies inside the control in your click event handler and, if it doesn't, set Capture to false and close the dropdown.
UPDATE:
You can also use the Control.Focused property to determine if the control has got or lost focus when using a keyboard or mouse instead of using the Capture with the same example provided in the MSDN Capture page.
Handle the Form's MouseDown event, or override the Form's OnMouseDown
method:
enter code here
And then:
protected override void OnMouseDown(MouseEventArgs e)
{
if (!theListBox.Bounds.Contains(e.Location))
{
theListBox.Visible = false;
}
}
The Contains method old System.Drawing.Rectangle can be used to indicate if
a point is contained inside a rectangle. The Bounds property of a Control is
the outer Rectangle defined by the edges of the Control. The Location
property of the MouseEventArgs is the Point relative to the Control which
received the MouseDown event. The Bounds property of a Control in a Form is
relative to the Form.
You are probably looking for the leave event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.leave.aspx
Leave occurs when the input focus leaves the control.
I just wanted to share this. It is probably not a good way of doing it that way, but looks like it works for drop down panel that closes on fake "MouseLeave", I tried to hide it on Panel MouseLeave but it does not work because moving from panel to button leaves the panel because the button is not the panel itself. Probably there is better way of doing this but I am sharing this because I used about 7 hours figuring out how to get it to work. Thanks to #FTheGodfather
But it works only if the mouse moves on the form. If there is a panel this will not work.
private void click_to_show_Panel_button_MouseDown(object sender, MouseEventArgs e)
{
item_panel1.Visible = true; //Menu Panel
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (!item_panel1.Bounds.Contains(e.Location))
{
item_panel1.Visible = false; // Menu panel
}
}
I've done this myself, and this is how I did it.
When the drop down is opened, register a click event on the control's parent form:
this.Form.Click += new EventHandler(CloseDropDown);
But this only takes you half the way. You probably want your drop down to close also when the current window gets deactivated. The most reliable way of detecting this has for me been through a timer that checks which window is currently active:
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
and
var timer = new Timer();
timer.Interval = 100;
timer.Tick += (sender, args) =>
{
IntPtr f = GetForegroundWindow();
if (this.Form == null || f != this.Form.Handle)
{
CloseDropDown();
}
};
You should of course only let the timer run when the drop down is visible. Also, there's probably a few other events on the parent form you'd want to register when the drop down is opened:
this.Form.LocationChanged += new EventHandler(CloseDropDown);
this.Form.SizeChanged += new EventHandler(CloseDropDown);
Just don't forget to unregister all these events in the CloseDropDown method :)
EDIT:
I forgot, you should also register the Leave event on you control to see if another control gets activated/clicked:
this.Leave += new EventHandler(CloseDropDown);
I think I've got it now, this should cover all bases. Let me know if I'm missing something.
If you have Form, you can simply use Deactivate event just like this :
protected override void OnDeactivate(EventArgs e)
{
this.Dispose();
}

Textbox value changed

Is it possible to know if any of the textbox values have changed in the application.
I have around 30 textboxes and I want to run a part of code only if, any of the textboxes value has changed out of the 30. Is there a way I can know that.
Each text box will raise an event TextChanged when it's contents have changed. However, that requires you to subscribe to each and every event.
The good news is that you can subscribe to the event with the same method multiple times. The handler has a parameter sender which you can use to determine which of your 30 text boxes has actually raised the event.
You can also use the GotFocus and LostFocus events to keep track of actual changes. You would need to store the original value on GotFocus and then compare to the current value on LostFocus. This gets round the problem of two TextChanged events cancelling each other out.
You can assign an event handler to each of the TextBox's TextChanged events. All of them can be assigned to the same event handler in code. Then you'll know when the text changes. You can set a boolean flag field in your class to record that a change occurred.
This is perhaps on the rough and ready side, but I did it this way.
In the constructor, I created
bool bChanged = false;
In the TextChanged event handler of each control (actually same for each), I put
bChanged = true;
When appropriate, I could do some processing, and set bChanged back to false.
You can also just do this:
In your Constructor:
MyTextBox.TextChanged += new TextChangedEventHandler( TextChanged );
And Then this Method:
private void TextChanged(object Sender, TextChangedEventArgs e){
//Do something
}
try this. Add this code to the load/constructor. no need to specify the event in the XAML explicitly
this.AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(TextChanged));
private void TextChanged(object Sender, TextChangedEventArgs e)
{
//ToDO (use sender to identify the actuale text from where it fired }
}

Triggering an event after a Winform layout is complete

I am working on a C# WinForm application.
I want to trigger some processing once the form has been "shown" and the layout of the form is complete.
I am using the "_Shown" event, but this seems to trigger before the layout of the form has completed. Is there event I can use that fires once the layout is complete?
Put Application.DoEvents() at the start of the form's Shown event handler. This will force all the controls to be rendered.
I don't see an event after Shown you can use for this purpose. Could you not use a timer to delay your processing in the Shown event?
An old trick in VB6 used to be to use the Paint event:
bool firstShown = false;
void form_Paint(Object sender, EventArgs e) {
if ( !firstShown ) {
YourMethodThatNeedsToRunOnShown();
firstShown = true;
}
//the rest of your paint method (if any)
}
It is a little hacky, but it does work
This works for me and is much less "hacky" than other suggestions:
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
if(someControl == null)
return; // be careful of OnLayout being called multiple times
// otherwise, do some stuff here, set control sizes, etc.
}
AS far as I can remember the event order is something like
Form.Load
Form.Layout
Form.VisibleChanged
Form.GotFocus
Form.Activated
Form.Shown
So if something is still happening after Form.Show it's because of the way you coded it.
Are you maybe creating the form dynamically?
The best solution is the Shown() event: http://msdn.microsoft.com/en-us/library/system.windows.forms.form.shown.aspx
"The Shown event is only raised the first time a form is displayed; subsequently minimizing, maximizing, restoring, hiding, showing, or invalidating and repainting will not raise this event."
Try using Form.GotFocus (inherited from control)..
something like this.
private void Form1_Load(object sender, EventArgs e)
{
this.GotFocus += new EventHandler(Form1_gotFocus);
this.Focus();
}
private void Form1_gotFocus(object sender, EventArgs e)
{
// You will need to Switch focus from form at the end of this function,
//to make sure it doesnt keep Firing.
}
According To msdn , the following happens:
When you change the focus by using the keyboard (TAB, SHIFT+TAB, and so on), by calling the Select or SelectNextControl methods, or by setting the ContainerControl..::.ActiveControl property to the current form, focus events occur in the following order:
Enter
GotFocus
Leave
Validating
Validated
LostFocus

Categories

Resources