0

I have been trying to run a very simple application that moves a 20 by 20 pixel square 20 pixels to the right on a canvas every second. I am using a dispatchertimer to fire the event every second.

The problem is that the square doesn't move to the right unless I shake the application window (with my mouse), and it occasionally moves on its own (albeit not every second).

I have already tried reinstalling Visual Studio 2017 and installing it on my SSD and HDD, neither seem to fix the issue.

Here is the full code of the application's MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    DispatcherTimer timer = new DispatcherTimer();
    Rectangle s = new Rectangle();
    Point currentPosition = new Point(20, 20);

    public MainWindow()
    {
        InitializeComponent();
        timer.Tick += Timer_Tick;
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Start();

        s.Width = 20;
        s.Height = 20;
        s.Fill = new SolidColorBrush(Colors.Black);

        map.Children.Add(s);
    }

    public void Timer_Tick(object sender, EventArgs e)
    {
        RedrawSquare(); 
    }

    public void RedrawSquare()
    {
        map.Children.Clear();

        s.Width = 20;
        s.Height = 20;
        s.Fill = new SolidColorBrush(Colors.Black);

        Canvas.SetLeft(s, currentPosition.X += 20);

        map.Children.Add(s);
    }
}

On the MainWindow.xaml file there is an empty Canvas with the name "map"

Thank you in advance

Supahotfire420
  • 441
  • 1
  • 4
  • 10
  • 1
    *"the square doesn't move to the right unless I shake the application window"* - can't reproduce the issue. The black square moves every second without any visible lag. You have some environment issues: old pc, viruses, background tasks, etc. So it's not really a programmer question. – Sinatr Oct 17 '18 at 15:08
  • My hardware is fine, and I have no viruses. CPU: i7 7700k GPU: gtx1080 I have already tried closing all background tasks that I possibly could, but it doesn't help. – Supahotfire420 Oct 17 '18 at 15:18
  • @Sinatr I was able to reproduce it, and my machine has very high specs and no viruses. Setting the timer priority fixed it though. – Shahin Dohan Oct 17 '18 at 15:28
  • DispatcherTimer would not be recommended for this kind of job. It is not prepared to be used for accurate rendering jobs, as it will always have the chance to introduce an indefinite amount of lag, no matter what. I would look into an animation to achieve this task or, if you required even more control, implement a custom Clock. These work on the rendering thread and are in sync with the actual rendered content. – Unknown Coder Oct 17 '18 at 21:47
  • 1
    A simpler (and perhaps not so prettier) approach, would be to subscribe to the `CompositionTarget.Rendering` event and measure elapsed time there to perform some rendering operation based on the elapsed render time. This approach is more similar to how games render, based on frames. – Unknown Coder Oct 17 '18 at 21:50

2 Answers2

2

You don't need to remove and add the Rectangle on each timer tick, or reset its properties each time.

Just increment the value of the Canvas.Left property:

public partial class MainWindow : Window
{
    private readonly DispatcherTimer timer = new DispatcherTimer();
    private readonly Rectangle s = new Rectangle();

    public MainWindow()
    {
        InitializeComponent();

        timer.Tick += Timer_Tick;
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Start();

        s.Width = 20;
        s.Height = 20;
        s.Fill = Brushes.Black;
        Canvas.SetLeft(s, 0);

        map.Children.Add(s);
    }

    public void Timer_Tick(object sender, EventArgs e)
    {
        Canvas.SetLeft(s, Canvas.GetLeft(s) + 20);
    }
}

The movement would however be much smoother with an animation:

public MainWindow()
{
    InitializeComponent();

    s.Width = 20;
    s.Height = 20;
    s.Fill = Brushes.Black;
    Canvas.SetLeft(s, 0);

    map.Children.Add(s);

    var animation = new DoubleAnimation
    {
        By = 20,
        Duration = TimeSpan.FromSeconds(1),
        IsCumulative = true,
        RepeatBehavior = RepeatBehavior.Forever
    };

    s.BeginAnimation(Canvas.LeftProperty, animation);
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I don't know, it doesn't want to move at all after this. – Supahotfire420 Oct 17 '18 at 14:47
  • The code works, check again if you did copy it exactly. – Clemens Oct 17 '18 at 14:51
  • I have the exact same copy, but it doesn't want to move at all. If I remove the timer and add the animation it throws an exception saying System.Windows.Media.Animation.AnimationException Cannot animate the property Left with the use of System.Windows.Media.Animation.DoubleAnimation. – Supahotfire420 Oct 17 '18 at 14:59
  • You forgot to set `Canvas.SetLeft(s, 0);` initially. The default value is `NaN` – Clemens Oct 17 '18 at 15:05
  • Okay that worked, but it's not what I want. I want the dispatchertimer to run every second, not every 10 or not at all – Supahotfire420 Oct 17 '18 at 15:08
  • 1
    It runs every second. What else is your application doing? Have you considered using an animation? – Clemens Oct 17 '18 at 15:10
  • Nothing besides that, i've tried it in different projects and it seems to be some form of lag that causes the events to not fire at all, or heavily delayed. – Supahotfire420 Oct 17 '18 at 15:13
  • There's probably something else wrong with your system. Have you seen the comment on the question? – Clemens Oct 17 '18 at 15:14
  • The animation on Clemens' answer runs every second and is the right way to do this in WPF. If you're not getting it to run every second with his code, you're having lag. You may be using software rendering or, if you are performing other operations on your window, they may be freezing the UI thread a bit. Also, I would recommend to begin the animation on the `Loaded` event. – Unknown Coder Oct 17 '18 at 22:02
2

You can try setting the DispatcherPriority to Normal.

Instantiate your timer like this:

DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Normal);

EDIT:

Although this somehow fixed the issue (square was moving without the need to move the window), it's apparently still the wrong answer. I don't know much about the DispatcherTimer, but I recall having changed the priority once but I don't remember why. In any case, it might be helpful to someone else.

Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58
  • I'm sorry, but why the downvotes? It works and is I think the correct solution (disregarding OP's implementation decisions) – Shahin Dohan Oct 17 '18 at 14:55
  • Well, I tried the fix on my machine, and it works. Second of all, I might be wrong, but Rendering sounds quite high priority for a desktop application, don't you think? – Shahin Dohan Oct 17 '18 at 14:58
  • It doesn't matter how it *sounds*. It is lower than normal. See here: https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherpriority?view=netframework-4.7.2 – Clemens Oct 17 '18 at 14:58
  • Alright, but it works regardless, so unless you want to criticize OP's code, this should work for his current code. If you'd like to explain why Render works and Normal doesn't, please do so. – Shahin Dohan Oct 17 '18 at 14:59
  • Oh my bad, I thought Normal was the default, apparently it's not. I'll edit my answer. – Shahin Dohan Oct 17 '18 at 15:01
  • Ahhh, sorry, my bad indeed. The default is Background, not Normal. But even then it still works with it. There is no need to change it. – Clemens Oct 17 '18 at 15:02
  • The default is indeed Background, and it causes OP's issue to happen, but setting it to Normal fixes the issue. Why can't this be the answer? – Shahin Dohan Oct 17 '18 at 15:05
  • "*and it causes OP's issue*" - not at all. You can easily animate UI elements with a DispatcherTimer at default priority. – Clemens Oct 17 '18 at 15:07
  • 1
    This is so very wrong - @Clemens posted the correct answer. – Peregrine Oct 17 '18 at 15:16
  • Alright, I'll edit my answer again. It would be nice to know why this is wrong though, we could all learn something :-) – Shahin Dohan Oct 17 '18 at 15:23
  • The problem with `DispatcherTimer` is that it is not designed to be accurate for rendering and it can suffer from indefinite lag, depending on how much the application or the system is overloaded. Animating something with `DispatcherTimer` may seem to work, but will sooner or later show its disadvantages, introducing unpredictable amounts of lag. WPF already has its own rendering engine that is ready to take every rendering issue into account and has its own dedicated thread. Using a `Clock` implementation is a way to interact with it. Animations also implement their Clock to do their jobs. – Unknown Coder Oct 17 '18 at 22:13
  • @A Silva Thank you for that explanation! Is it ok to use DispatcherTimer to update a textblock that shows the time every second? – Shahin Dohan Oct 18 '18 at 06:11
  • Accepted answer seems overly complicated. I use this for my stopwatch. Just don't use it to count, use the StopWatch class instead and get the elapsed time from there as a TimeSpan. Way easier and works flawlessly with this answer. – Baxorr Jun 26 '20 at 16:47