I'm new with XNA and C#, and I've come to the point in my XNA project where I need event handlers to predict when the game loses focus so that I can sync music and visuals once it gains focus again. But I got one problem; the game does not "update" while being dragged, but I can't seem to find an suitable event listener for this. I've tried:
System.Windows.Forms.Control.FromHandle(Window.Handle).Move += new EventHandler(DeactivateGame);
This one calls "DeactivateGame" tons of times while moving the window. But even if it works, despite the fact it calls the function more than once, I can't see a event handler that calls a function when the window handle is released so that the game can resume again by calling "ActivateGame"
A sidenote (if it helps);
this.Activated += new EventHandler<EventArgs>(NotifyActivated);
this.Deactivated += new EventHandler<EventArgs>(NotifyDeactivated);
These event handlers works fine when minimizing the window or putting focus on something else than the game window, but it does not register the window being dragged. Maybe obvious for the one used to programming, but I just want to make sure I've given enough information
EDIT:
The function I want to add as the result of the event handler is a DateTime/TimeSpan that gets called when the window is out of focus or dragged. When dropped or gets focus again, this will compare the current time with the time set when the window lost focus to calculate the lost time in between.
For detecting when the XNA window is being dragged, you were on the right track using the Window.Handle with Windows Forms. You can simply listen to the ResizeBegin and ResizeEnd events to know when the user starts moving the window and when they release it.
var xnaWinForm = (System.Windows.Forms.Control.FromHandle(Window.Handle) as System.Windows.Forms.Form);
if (xnaWinForm != null)
{
xnaWinForm.ResizeBegin += new EventHandler(xnaWinForm_ResizeBegin);
xnaWinForm.ResizeEnd += new EventHandler(xnaWinForm_ResizeEnd);
}
And here's what the event handlers look like.
void xnaWinForm_ResizeBegin(object sender, EventArgs e)
{
// XNA window is starting to be moved.
}
void xnaWinForm_ResizeEnd(object sender, EventArgs e)
{
// XNA window was released and is no longer being moved.
}
Then just combine this with the other events you mentioned for determining when the window is minimized/restored/active to determine how long the window has been "inactive" for.
Related
I have a listview in which I display objects with their state and name. The state is surrounded by a button and when I click on it the state changes from active to inactive or the other way round.
Now I want to change the state when I click the mouse button down and change it back when the mouse button goes up. My button has to event "PreviewMouseLeftButtonDown" and "PreviewMouseLeftButtonUp" as you can see in the code below. In the SendStateMessage() method the state is send to a SCADA-Server and the server sends me the new state back and the GUI edits the change.
Normally it doesn't make any problems, but when I click to fast the "PreviewMouseLeftButtonUp" event doesn't get called. So the "MouseUp" is not shown at the consol.
I tried to call a Task.Delay() in the first mouseDown event because i thougt that the method needs some more time but this didn't work well. So i want to ask you if somebody has an idea what the problem could be and a way how this issue can be fixed.
Thanks for your time.
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//_CurrentItem is an object of the class VM on which the click is executed.
_CurrentItem = sender as FrameworkElement).DataContext as VM
_CurrentItem.SendStateMessage();
}
private void Button_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("MouseUp");
}
EDIT:
After a bit of time passing by I had another idea to solve the problem, which is also working. I only wait for the MouseDownEvent and if this gets triggered i have a loop where i wait as long as the mouse is pressed, when the mouse is released i could leave the loop and there i inserte the code which i wanted to do in the MouseUPEvent.
You can also wait for the MouseDownEvent and if this gets triggered make a loop where you wait as long as the mouse is pressed, when the mouse is released you could leave the loop and inserte there your code which you wanted to do in the MouseUpEvent.
In my case another control consumed the mouseup event. So I catch the mouseup event on level of the top most windows.
I'm using the MouseLeave event to check if the user left my form and to close my window, but using
this.MouseLeave += new System.EventHandler(this.InvisibleForm_Leave);
is too slow, only if I'm going to leave my form slowly the event is fired, moving it in a normal way / a little bit faster I don't get a leave event.
Therefore I tried to check on my own if the mouse left my form or not:
private void checkPos()
{
Rectangle rec = this.Bounds;
while (true)
{
Point point = new Point(Cursor.Position.X, Cursor.Position.Y);
if (!rec.Contains(point))
{
Console.WriteLine("leaving");
this.Close();
}
Thread.Sleep(100);
}
}
started in a own thread after creating the form:
public MyForm()
{
InitializeComponent();
Thread m_mouseListenerThread = new Thread(new ThreadStart(this.checkPos));
m_mouseListenerThread.Start();
}
But with this I have more or less the same problem, leaving the area still returns true after checking it with rec.Contains(point) only after a second he is going to execute the if code, but sometimes he's getting it in an instant.
The second problem with this is that I'm getting a thread exception in the this.Close(); line in the checkPost() method:
Cross-thread operation not valid: Control 'MyForm' accessed from a thread other than the thread it was created on.
Now I don't really know how to implement the mouse leaving part in another way.
I don't think MouseLeave performance is the issue here. I've used the MouseLeave (in combination with MouseEnter and MouseMove) to automatically fade in/out forms. It works :).
Here's a sample form with just a Label:
If MouseLeave is handled for both the Label and the Form, the event handler always fires regardless of how fast I move the mouse. For example:
this.label1.MouseLeave += new System.EventHandler(this.HandleMouseLeave);
this.MouseLeave += new System.EventHandler(this.HandleMouseLeave);
private void HandleMouseLeave(object sender, EventArgs e)
{
Debug.WriteLine(string.Format("MouseLeave: {0}", DateTime.Now));
}
However, if I remove the MouseLeave handler for label1, I am able to reproduce the behavior that you are seeing. If I move the mouse slowly from label1 (orange) to the form (green) and outside, the event fires. If I move the mouse quickly from label1 to outside of the form, the event does not fire.
So, what I think is happening is that a child control of your form is firing a MouseLeave event, and you are not handling that event. The reason you see the event fire when you move the mouse slowly is because you are hovering over the form area long enough to produce the event.
Further, spawning a separate thread to monitor MouseLeave events is not a good approach. Your performance will suffer as this thread polls for an event state (as opposed to waiting for an event), you are creating an unnecessary headache of starting/stopping the threads, and you will need to invoke back onto the UI thread whenever you want to do anything with the form (as you have learned). If you have the time to revisit the MouseLeave event approach, I would highly recommend that you do so. Good luck!
For the mouse leaving part, I am not quite sure. Maybe you can try to handle that by MouseMove event?
For the invalid cross-thread operation issue, you simply cannot access a control which is owned by another thread (it's the UI thread in your case). Use Control.BeginInvoke or Control.Invoke instead.
I faced same problem, do that:
put timer in form.
Put your code in timer tick event like mouse leave:
Label1.BackColor=Color.PaleGreen;
Set timer interval to less than 30
Use this function
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
timer1_Tick(label1(example), e);
}
Put this in formload event
timer1.Tick += timer1_Tick;
the code will run very quickly and easy , you will never see any problem like this again
Our application uses an animation to fade out a message window when it's closed. To do this, we override OnClosing, and if we haven't completed our animation, we cancel the close and start the animation. When the animation completes, we close the window. This seems to be a fairly common pattern based on what I've seen online, since Closing and Closed aren't routable events.
The problem is, we occasionally see a window get "stuck" - the animation doesn't seem to occur, so the "finished" flag never gets set and the window just sits there, canceling any close events. Sometimes, the animation eventually kicks off and the window closes, but other times it seems like its permanently stuck (though obviously you can't tell for sure...halting problem and all that...).
Does anyone have any thoughts as to why the Storyboard won't get kicked off even after calling Begin()? Without having been able to dig in really deep into the Storyboard code/data structures, it feels like it's waiting for something to happen before it actually kicks off, that never happens.
Here are some of the possibilities that I've ruled out so far:
incorrect threading (all windows, animations, storyboard, etc. are created/handled/accessed on the GUI thread)
GUI thread blocked (clicking the X still fires the Closing event, and the GUI thread responds to events in other windows, system tray, etc.)
GUI thread busy (we're only doing UI work on the GUI thread, and we generally only have one window open and at most one "close" animation running at a time)
That said, here are some things that might be contributing:
Windows can close each other (in some cases if one is already open, the new one will close the existing one)
We also have a "fade in" animation bound to the Loaded event via XAML, but that seems to complete correctly.
We recently removed our "main" UI window, so the application's main window is now a dummy hidden window.
I suspect that last one most strongly because it was added most recently, and this behavior was never reported/noticed before that change (though I can't say definitively it never happened before that change). But other GUI/window-related events still all work, and the fade-out animation logic works most of the time.
Finally, here's the meat of our code for doing the closing animation:
public class MyWindow : Window {
private bool _storyBoardCompleted;
private Storyboard _closingStoryBoard;
protected Storyboard GetClosingStoryBoard()
{
Grid mainGrid = (Grid)FindName("MainGrid");
DoubleAnimation closingAnimation = new DoubleAnimation();
closingAnimation.From = 1;
closingAnimation.To = 0;
closingAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(closingAnimation, mainGrid);
Storyboard.SetTargetProperty(closingAnimation, new PropertyPath(OpacityProperty));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(closingAnimation);
storyboard.Completed += StoryboardCompleted;
return storyboard;
}
private void StoryboardCompleted(object sender, EventArgs e)
{
_storyBoardCompleted = true;
Close();
}
protected override void OnClosing(CancelEventArgs e)
{
if (_closingStoryBoard == null)
{
_closingStoryBoard = GetClosingStoryBoard();
_closingStoryBoard.Begin();
}
if (!_storyBoardCompleted)
{
e.Cancel = true;
}
}
}
EDIT:
The issue seems to pop up whenever we open two of these windows very close together in time, and close the first before showing the second. Essentially:
window1.Show()
window1.Close()
window2.Show()
At this point, if we try to close window2, it will cancel the close as in the code above, but the animation won't begin (hence the problem). If we later on call window2.Close() followed closely by window3.Show() (i.e. show a third window), then window2's animation finishes and the window closes - but then window3 is stuck in the same state. So the behavior almost seems to get "passed on" from window to window once we are in this state.
EDIT:
I've ruled out another possibility - I was wondering whether the "fade-in" storyboard of the second window was somehow conflicting with the "fade-out" storyboard of the first, and somehow affecting the second window's fade-out. But the problem occurs even after removing the fade-in storyboard.
Furthermore, the problem goes away entirely if I replace the fade-out storyboard with just a half-second dispatch timer (i.e. just a delayed close, no animation). Just a sanity check that it definitely has something to do with the animation...
Did you ever try adding parameters to the Begin method:
_closingStoryBoard.Begin(this);
I never could quite pin down specifically why this was happening, but it seemed to be related to the fact that I was showing the second window while the first one was still animating. I still don't understand why this caused problems, but I was able to work around it by waiting for the first one to close completely before showing the second (basically hooking onto the first window's Closed event).
Would still love to know why this behaves this way, in case the workaround of "wait for the first window to close" ever stops being sufficient...
Is there any event in C# that fires when the form STOPS being moved. Not while its moving.
If there is no event for it, is there a way of doing it with WndProc?
The ResizeEnd event fires after a move ends. Perhaps you could use that.
This is not a failsafe solution, but it's pure .NET and it's dead simple. Add a timer to your form, set it to a relatively short delay (100-150 ms seemed OK for me). Add the following code for the Form.LocationChanged and Timer.Tick events:
private void Form_LocationChanged(object sender, EventArgs e)
{
if (this.Text != "Moving")
{
this.Text = "Moving";
}
tmrStoppedMoving.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
tmrStoppedMoving.Start();
this.Text = "Stopped";
}
If you want more exact handling (knowing exactly when the mouse button is release in the title bar and such) you will probably need to dive into monitoring windows messages.
I had the same problem with a user control, but it does not have the ResizeEnd event. The solution, which worked is to override the WndProc method and listen for EXITSIZEMOVE.
See example here
Just set a flag to true when onmove events are fired. If a mouseup event happens and the flag is true, the form stopped being moved.
I admit this probably won't work in the case of a user moving a form via the keyboard, but that's pretty rare.
I tested ResizeChanged event, and it works fine, however I don't know relation between move and resize, but it works for me
On my main form, there is another (floatable) window. This floatable window works sort of like a popupwindow in that it will close when the user clicks somewhere else outside of this window. This is handled by the Deactivate event. But what I want to do is, if the user clicks on a different control (say a button), I want to both close this float window and then activate that button with just one click. Currently, the user has to click twice (one to deactivate the window and once more to activate the desired button). Is there a way to do this with just one click?
foreach(Control c in parentForm.Controls)
{
c.Click += delegate(object sender, EventArgs e)
{
if(floatyWindow != null && floatyWindow.IsFloating)
{
floatyWindow.Close();
}
};
}
And then add your handlers as normal. This additional handler can close the floaty window.
Make sure you floaty window isn't a dialog too as this will not allow your parent form's controls to be clicked.
I had a slightly hacky solution. In your Deactivate event, fire another custom event to your main form. Then when you main form is handling the custom event, enumerate through your control(this.Controls) and locate the control under the mouse by checking all their bound then call Focus(). You might need to sort by the one with the smallest surface area, or you can have a separate list of "focus-able" control like button just for this purpose.
Another way might be to switch focus to your main form immediately after OnMouseLeave of the floatable window, or OnMouseHover of your main window, but keep the floatable windows on top, just no focus. Handle the global mouse down of your main form, and close the floatable window by then.
These are just theories, not tested.
I had an issue like this once too, when a customer wanted "floaty" windows all over there application. I used used an approach similar to the one described in this article:
http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/article.asp
Code sample available here:
http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/Popup_Form_Demonstration.asp
By extending this a bit we created "floaty" windows similar to the ones VS uses when you get a runtime error while debugging code.
At the very least reading the code may give you some insight, however, quarrelsome's response may be the more simple solution.