Picture box moves with timer, but it lags behind? - c#

I'm not sure how to explain this. I have the code set up so when the timer ticks, it checks to see if the picture box is at a certain location. If it isn't, then it should move a little bit closer. It works in moving, but each time it moves, it generates a white field behind it, and it grows. I have the tick set to 750, so each time it ticks, the picture moves farther than it should, and each time it goes farther than the previous time. Here's the code.
private void ZombieTimer1_Tick(object sender, EventArgs e)
{
ZombieTimer1.Tick += new System.EventHandler(ZombieTimer1_Tick);
enemyNPC1.Show();
ZombieTimer1.Start();
if (enemyNPC1.Location.X < 280)
{
enemyNPC1.Left = enemyNPC1.Left + 1;
ZombieTimer1.Stop();
ZombieTimer1.Start();
}
}

Move the declaration of the event handler to the constructor for your window.
At the moment you get another event handler every time the event fires, which causes the event to fire twice the second time, and four times the next time, etc.

Related

How to have multiple cases inside PaintEvent - using Timer

I'd like to use multiple cases inside PaintEvent, switched using Timer:
protected override void OnPaint(PaintEventArgs e)
{
switch (scene)
{
case 1:
foreach (Tunnels boxes in tunnellist)//draws series of boxes, creating "tunneleffect" by looping using Invalidate();
{
boxes.Draw(e.Graphics);
Invalidate();
}
break;
case 2:
foreach (....//here I would like to have some other effect, for example drawing list of sprites moving using sine-waves etc.
break;
default:
break;
}
}
and in Timer I have following (ticking every 20 seconds):
private void timer1_Tick(object sender, EventArgs e)
{
if (scene > 1)
{
scene = 1;
}
else
{
scene++;
}
this.Invalidate();
}
The problem here is that as I have the Invalidate() in PaintEvent, the timer never fires. Any ideas how to tackle this?
Control.Invalidate:
Invalidates a specific region of the control and causes a paint message to be sent to the control.
Why would you want to send a paint message inside the paint event handler? Why not just start painting?
It depends a bit of which Control you are paining. Are the boxes that you draw in the same Control? Then it will be sufficient to draw the boxes. No need to invalidate again.
If you want to draw the boxes in a different control than the Control that you are painting right now, you should not invalidate this Control, but the Control in which the Boxes must be drawn. This will lead to an Onpaint for that Control. Use that event to paint the Boxes without Invalidate.
The problem here is that if I dont't Invalidate() in paint event I end up having static picture of the boxes forming the tunnel, ie. no animation.
I'm painting straight to Form canvas (=control).
I tried to make a workaround having two timers, one ticking every 20 secs (changing scenes) and other every 20 millisec (Invalidating). However, this resulted in jerky motion.

MouseWheel event not registering minor movements

I thought I would improve my program by adding granular scrolling, hence I am studying up on the mouse wheel delta.
I have a new Logitech mouse with smooth scrolling enabled. Testing the scrolling on Chrome and I barely have to touch the scroll wheel and the page scrolls up one pixel. Nice!
I have a test program in c# with the MouseWheel event connected to a form with a few labels. Nothing fancy:
private void MainForm_MouseWheel(object sender, MouseEventArgs e)
{
int lines = e.Delta / 120;
if (lines > MaxLines)
MaxLines = lines;
else if (lines < MinLines)
MinLines = lines;
deltaResult.Text = e.Delta.ToString();
linesResult.Text = lines.ToString();
minLinesResult.Text = MinLines.ToString();
maxLinesResult.Text = MaxLines.ToString();
}
Trouble is this event only gets called when the delta reaches 120 (+/-). I understand that the mouse should be firing more events with lower value deltas, which it apparently works for Chrome, but for the test code, I have to move the scroll wheel considerably further before the event is fired.
I have come to the conclusion that either granular scrolling is not possible in C#; or that there is another event I should be calling; or it is something that needs to be enabled within the program; or that there is another method somewhere adding up the Deltas until they reach 120 and then firing the MouseWheel event.
My specific question:
How do I get the MouseWheel event to fire with only a minute rotation of the mouse wheel such as Chrome is obviously doing? If this is not possible, what event should I be calling?

WPF DoubleAnimationUsingKeyFrames skipping frames

I am attempting to animate a slider using DoubleAnimationUsingKeyFrames and DiscreteDoubleKeyFrame to animate a slider for each key frame. However, it seems to be skipping frames when the storyboard is played, that is, the ValueChanged event is not firing for every key frame. The storyboard and animation are set up in the code-behind as follows:
DoubleAnimationUsingKeyFrames _timelineAnimation = new DoubleAnimationUsingKeyFrames();
Storyboard _timelineStoryboard = new Storyboard();
void SetupTimeline()
{
// set up timeline storyboard animation
_timelineAnimation.SpeedRatio = 1.0;
Storyboard.SetTarget(_timelineAnimation, timelineSlider);
Storyboard.SetTargetProperty(_timelineAnimation, new PropertyPath(Slider.ValueProperty));
_timelineStoryboard.Children.Add(_timelineAnimation);
timelineSlider.ValueChanged += TimelineSlider_ValueChanged;
}
void StartTimeline(List<double> times)
{
foreach (double time in times)
{
double value = time - timelineSlider.Value;
var keyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(value));
_timelineAnimation.KeyFrames.Add(new DiscreteDoubleKeyFrame(time, keyTime));
}
_timelineStoryboard.Begin(timelineSlider, true);
}
// this does not fire for every key frame
void TimelineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Debug.Print($"TimelineSlider_ValueChanged {e.NewValue}");
}
Example input data (in seconds):
136.224
136.238
136.244
136.2441
136.246
136.2461
136.264
136.274
136.294
136.2941
136.296
136.2961
I imagine that the problem may be that the data items are too close together. Is there any way to speed up the resolution of the animation timer? Or any other suggestions as to a solution?
In fact, when the interval between two frames is set below 30 milliseconds or so, some frames might be skipped.
This is much like a WPF DispatcherTimer. A WPF DispatcherTimer, no matter how small its Interval is, only ticks once in every (about) 30 milliseconds.
To produce animation effect, WPF uses a time manager to signal an update periodically. When it ticks, the animated properties (in this case, the Value property of the Slider) are re-evaluated and the UI is updated. And it apparently picks the “most recent” frame in the timeline and discard those are already outdated.
The time manager "ticks" many times per second; the actual number of ticks that occur each second varies depending on available system resources.
Even if you manage to speed up the resolution timer - I guess it is not possible for a UI timer, say, make it tick every 1 millisecond, human eyes can't perceive such a high frequency in the animation, and, monitors display at only 50-60 Hz.

The reason behind slow performance in WPF

I'm creating a large number of texts in WPF using DrawText and then adding them to a single Canvas.
I need to redraw the screen in each MouseWheel event and I realized that the performance is a bit slow, so I measured the time the objects are created and it was less than 1 milliseconds!
So what could be the problem? A long time ago I guess I read somewhere that it actually is the Rendering that takes the time, not creating and adding the visuals.
Here is the code I'm using to create the text objects, I've only included the essential parts:
public class ColumnIdsInPlan : UIElement
{
private readonly VisualCollection _visuals;
public ColumnIdsInPlan(BaseWorkspace space)
{
_visuals = new VisualCollection(this);
foreach (var column in Building.ModelColumnsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = "C" + Convert.ToString(column.GroupId);
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, columntextsize, columntextcolor,
null, TextFormattingMode.Display)
{
TextAlignment = TextAlignment.Left
};
// Apply Transforms
var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
dc.PushTransform(st);
// Draw Text
dc.DrawText(ft, space.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
And this code is run each time the MouseWheel event is fired:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
What could be the culprit?
I'm also having trouble while panning:
private void Workspace_MouseMove(object sender, MouseEventArgs e)
{
MousePos.Current = e.GetPosition(Window);
if (!Window.IsMouseCaptured) return;
var tt = GetTranslateTransform(Window);
var v = Start - e.GetPosition(this);
tt.X = Origin.X - v.X;
tt.Y = Origin.Y - v.Y;
}
I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.
I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).
I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.
I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.
So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.
Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:
I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
base.OnMouseMove(e);
}
}
The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.
The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
POINT screenSpacePoint;
GetCursorPos(out screenSpacePoint);
// note that screenSpacePoint is in screen-space pixel coordinates,
// not the same WPF Units you get from the MouseMove event.
// You may want to convert to WPF units when using GetCursorPos.
_mousePos = new System.Windows.Point(screenSpacePoint.X,
screenSpacePoint.Y);
// Update my WriteableBitmap here using the _mousePos variable
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
private bool _bitmapNeedsUpdate;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (!_bitmapNeedsUpdate) return;
_bitmapNeedsUpdate = false;
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
_bitmapNeedsUpdate = true;
base.OnMouseMove(e);
}
}
Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.
#Vahid: the WPF system is using [retained graphics]. What you eventually should do, is devise a system where you only send "what has changed compared to previous frame" - nothing more, nothing less, you should not be creating new objects at all. It's not about "creating objects takes zero seconds", it's about how it affects rendering and the time. It's about letting the WPF do it's job using caching.
Sending new objects to the GPU for rendering=slow. Sending only updates to the GPU which tells what objects moved=fast.
Also, it's possible to create Visuals in an arbitrary thread to improve the performance (Multithreaded UI: HostVisual - Dwayne Need). That all said, if your project is pretty complex in 3D wise - there's good chance that WPF won't just cut it. Using DirectX.. directly, is much, much, more performant!
Some of the articles I suggest you to read & understand:
[Writing More Efficient ItemsControls -
Charles Petzold] - understand the process how one achieves better drawing rate in WPF.
As for why your UI is lagging, Dan answer seems to be spot on. If you are trying to render more than WPF can handle, the input system will suffer.
The likely culprit is the fact that you are clearing out and rebuilding your visual tree on each wheel event. According to your own post, that tree includes a "large number" of text elements. For each event that comes in, each of those text elements must be recreated, reformatted, measured, and eventually rendered. That is not the way to accomplish simple text scaling.
Rather than setting a ScaleTransform on each FormattedText element, set one on the element containing the text. Depending on your needs, you can set a RenderTransform or LayoutTransform. Then, when you receive wheel events, adjust the Scale property accordingly. Don't rebuild the text on each event.
I would also do what other have recommended and bind an ItemsControl to the list of columns and generate the text that way. There is no reason you should need to do this by hand.

On Idle Hide Mouse System-Wide, Show on Movement, from a C# Application

I'm trying to develop an application that, when running, will hide the mouse cursor wherever it happens to be on the screen after 5 seconds of inactivity, even if my app isn't in the foreground. Then, once the mouse moves again, the mouse should reappear instantly.
I've been reading a lot about low-level mouse hooks, but can't seem to wrap my head around the delegates and callbacks that, to me, make the code a lot harder to read.
Here are some of the things I've been looking at in terms of solutions:
Hide mouse cursor after an idle time
http://blogs.msdn.com/b/toub/archive/2006/05/03/589468.aspx
http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C
And I seemed to get closest with this one:
http://weblogs.asp.net/jdanforth/archive/2011/03/19/detecting-idle-time-with-global-mouse-and-keyboard-hooks-in-wpf.aspx
My problem seems to be that if I set up a timer on my main form for 5 seconds, then yes the mouse disappears after those 5 seconds if it's inactive, but then it also takes 5 seconds for the mouse to reappear! Would the solution simply be to have two separate timers and separate events?
Thanks so much for the help!
Add a handler for the MouseMove event on your form then call Cursor.Show() in the MouseMove handler.
You dont have to create 2 different timers. I would use the same timer, but have a private member variable that keeps track of whether the cursor is hidden. Once you hide it, you can reset the timer to be a lot shorter (say 500 ms) and continue to track it to turn it back on.
You can try this:
private Timer t;
void OnLoad(object sender, EventArgs e)
{
t = new Timer();
t.Interval = 5000;
t.Tick += new EventHandler(t_Tick);
}
private bool _hidden = false;
void t_Tick(object sender, EventArgs e)
{
if(!_hidden)
{
Cursor.Hide();
t.Interval = 500;
}
else
{
if(--some parameter---)
Cursor.Show();
}
}

Categories